Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Problem Details no Node.js

Atualizado em: 12 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

O Problem Details no Node.js padroniza respostas de erro HTTP usando o media type application/problem+json. Em vez de cada endpoint inventar campos como error, message, code e reason, a API usa uma estrutura comum com type, title, status, detail e instance.

O formato é definido pela RFC 9457, que substituiu a RFC 7807. Ele não muda o status HTTP nem transforma detalhes internos em informações públicas. O objetivo é oferecer uma representação previsível para clientes humanos e máquinas, permitindo extensões específicas como erros de validação, saldo insuficiente ou conflito de versão.

Neste guia, você aprenderá a implementar Problem Details em Node.js, Express e Fastify, criar tipos estáveis, tratar validação, esconder dados sensíveis, documentar com OpenAPI e testar o contrato.

O que é RFC 9457?

A RFC 9457 define um objeto JSON para representar detalhes de problemas em APIs HTTP. O formato usa:

Content-Type: application/problem+json

Exemplo:

{
  "type": "https://api.exemplo.com/problems/insufficient-credit",
  "title": "Crédito insuficiente",
  "status": 403,
  "detail": "O saldo atual não permite concluir a compra.",
  "instance": "/problems/01JXYZ",
  "balance": 30,
  "required": 50
}

Campos padrão

  • type: URI que identifica o tipo do problema;
  • title: resumo curto e estável;
  • status: status HTTP, usado como conveniência;
  • detail: explicação específica da ocorrência;
  • instance: identificador da ocorrência.

Clientes devem usar type como identificador principal, não analisar o texto de detail.

O status real continua importante

O campo status não substitui a linha HTTP:

HTTP/1.1 422 Unprocessable Content

Os dois valores precisam ser iguais. Proxies, caches e bibliotecas genéricas usam o status HTTP real.

Tipo about:blank

Quando type está ausente, o valor implícito é about:blank. Nesse caso, o problema não possui semântica adicional além do status.

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404
}

Para erros de domínio que os clientes precisam tratar, crie uma URI própria e estável.

Classe ProblemDetails

export class ProblemDetails extends Error {
  constructor({
    type = 'about:blank',
    title,
    status = 500,
    detail,
    instance,
    extensions = {},
    cause
  }) {
    super(detail ?? title, { cause });
    this.name = 'ProblemDetails';
    this.type = type;
    this.title = title;
    this.status = status;
    this.detail = detail;
    this.instance = instance;
    this.extensions = extensions;
  }

  toJSON() {
    return {
      type: this.type,
      title: this.title,
      status: this.status,
      ...(this.detail ? { detail: this.detail } : {}),
      ...(this.instance ? { instance: this.instance } : {}),
      ...this.extensions
    };
  }
}

Veja Tratamento de Erros no Node.js para classes, cause e logging.

Factory de problemas

export const problems = {
  notFound(resource, id) {
    return new ProblemDetails({
      type: 'https://api.exemplo.com/problems/not-found',
      title: 'Recurso não encontrado',
      status: 404,
      detail: `${resource} não foi encontrado.`,
      extensions: { resource, id }
    });
  },

  conflict(detail, version) {
    return new ProblemDetails({
      type: 'https://api.exemplo.com/problems/conflict',
      title: 'Conflito de atualização',
      status: 409,
      detail,
      extensions: { currentVersion: version }
    });
  }
};

Não exponha IDs ou valores que o usuário não tem autorização para conhecer.

Middleware no Express

app.use((error, req, res, next) => {
  if (res.headersSent) return next(error);

  const problem = error instanceof ProblemDetails
    ? error
    : new ProblemDetails({
        type: 'https://api.exemplo.com/problems/internal-error',
        title: 'Erro interno',
        status: 500,
        detail: 'Não foi possível concluir a operação.'
      });

  const instance = problem.instance ??
    `/problems/${crypto.randomUUID()}`;

  logger.error({
    error,
    problemType: problem.type,
    instance,
    requestId: req.id
  }, 'Falha na requisição');

  res
    .status(problem.status)
    .type('application/problem+json')
    .send({ ...problem.toJSON(), instance });
});

O stack permanece apenas nos logs internos.

Error handler no Fastify

fastify.setErrorHandler((error, request, reply) => {
  const problem = error instanceof ProblemDetails
    ? error
    : new ProblemDetails({
        type: 'https://api.exemplo.com/problems/internal-error',
        title: 'Erro interno',
        status: 500,
        detail: 'Não foi possível concluir a operação.'
      });

  request.log.error({ error }, 'request_failed');

  reply
    .code(problem.status)
    .type('application/problem+json')
    .send(problem.toJSON());
});

Consulte Fastify com Node.js para hooks e schemas.

Erros de validação

A RFC permite extensões. Um formato útil:

{
  "type": "https://api.exemplo.com/problems/validation-error",
  "title": "Requisição inválida",
  "status": 422,
  "errors": [
    {
      "pointer": "#/email",
      "detail": "deve ser um e-mail válido"
    },
    {
      "pointer": "#/items/0/quantity",
      "detail": "deve ser maior que zero"
    }
  ]
}

Use JSON Pointer para indicar o campo. Não coloque regras internas ou regex sensíveis.

400 ou 422?

Use 400 quando a requisição não pode ser interpretada ou possui sintaxe inválida. Use 422 quando o conteúdo é compreendido, mas viola regras de validação ou domínio. Mantenha a política consistente.

401 e 403

  • 401: autenticação ausente ou inválida;
  • 403: identidade conhecida sem permissão.

Não revele se um recurso privado existe quando isso cria enumeração.

404

Um problema 404 pode incluir apenas uma descrição genérica. Evite retornar consultas SQL, caminhos internos ou detalhes sobre soft delete.

409 Conflict

Use para conflito de versão, estado ou recurso duplicado:

{
  "type": "https://api.exemplo.com/problems/version-conflict",
  "title": "Versão desatualizada",
  "status": 409,
  "detail": "O recurso foi alterado por outra operação.",
  "currentVersion": 8
}

Veja Lock Otimista no Node.js.

429 Too Many Requests

Inclua Retry-After quando apropriado:

reply.header('Retry-After', '60');
{
  "type": "https://api.exemplo.com/problems/rate-limit",
  "title": "Muitas requisições",
  "status": 429,
  "detail": "Tente novamente após o intervalo indicado."
}

Consulte Rate Limiting em APIs Node.js.

503 Service Unavailable

Use quando a dependência ou serviço está temporariamente indisponível. Não converta todo erro desconhecido em 503; isso pode incentivar retries perigosos.

Type URI

Prefira URI absoluta:

https://api.exemplo.com/problems/payment-declined

A página pode documentar:

  • significado;
  • status recomendado;
  • extensões;
  • ações do cliente;
  • exemplos;
  • histórico de mudanças.

Alterar a URI cria um novo tipo e pode ser breaking change.

Instance

instance identifica uma ocorrência:

/problems/01JABCDEF

O identificador pode ser correlacionado com logs, mas não deve permitir acesso público a detalhes sem autenticação.

Request ID e trace ID

Uma extensão pode oferecer correlação:

{
  "instance": "/problems/01JABCDEF",
  "traceId": "8d2f..."
}

Avalie se expor trace ID é aceitável. O cliente não precisa receber dados sobre topologia interna.

Internacionalização

title e detail podem ser localizados conforme Accept-Language. O cliente deve continuar usando type, status e extensões para lógica.

Negociação de conteúdo

Responda com application/problem+json. Mesmo quando o cliente aceita JSON genérico, o media type específico comunica o contrato.

OpenAPI

components:
  schemas:
    ProblemDetails:
      type: object
      properties:
        type:
          type: string
          format: uri-reference
        title:
          type: string
        status:
          type: integer
          minimum: 100
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri-reference
      required: [type, title, status]

  responses:
    ValidationProblem:
      description: Requisição inválida
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'

Veja OpenAPI com Node.js.

Clientes tipados

O cliente deve tratar tipos conhecidos e ter fallback:

switch (problem.type) {
  case 'https://api.exemplo.com/problems/validation-error':
    showValidation(problem.errors);
    break;
  case 'https://api.exemplo.com/problems/rate-limit':
    scheduleRetry(response.headers.get('retry-after'));
    break;
  default:
    showGenericError(problem.title);
}

Ignore extensões desconhecidas para permitir evolução compatível.

Logging

Registre:

  • type;
  • status;
  • instance;
  • request ID;
  • trace ID;
  • erro original;
  • rota normalizada;
  • versão do serviço.

Não copie automaticamente o corpo da requisição.

Segurança

  • Não retorne stack.
  • Não retorne SQL.
  • Não exponha caminhos.
  • Não revele tokens.
  • Não detalhe dependências internas.
  • Redija dados pessoais.
  • Use mensagens genéricas em 500.

Testes

test('retorna Problem Details na validação', async () => {
  const response = await app.inject({
    method: 'POST',
    url: '/orders',
    payload: { items: [] }
  });

  assert.equal(response.statusCode, 422);
  assert.match(
    response.headers['content-type'],
    /application\/problem\+json/
  );

  const body = response.json();
  assert.equal(body.status, 422);
  assert.equal(
    body.type,
    'https://api.exemplo.com/problems/validation-error'
  );
  assert.ok(Array.isArray(body.errors));
});

Consulte Node Test Runner.

Testes de contrato

Valide que:

  • status real e campo status coincidem;
  • type é estável;
  • content-type está correto;
  • campos obrigatórios existem;
  • stack nunca aparece;
  • extensões possuem tipos corretos;
  • clientes ignoram campos novos.

Erros comuns

  • Usar detail como código: texto pode mudar.
  • Type aleatório: clientes não conseguem mapear.
  • Status divergente: intermediários interpretam errado.
  • Stack no corpo: informação sensível é exposta.
  • 200 com erro: semântica HTTP é quebrada.
  • Extensões sem documentação: contrato fica implícito.
  • Um tipo para tudo: clientes perdem precisão.
  • Tipos demais: manutenção vira complexa.

Conclusão

O Problem Details no Node.js oferece um contrato consistente para erros HTTP. A RFC 9457 define campos estáveis, extensões e um media type específico sem substituir os status existentes.

Use type URIs documentadas, esconda internals, mantenha status coerentes e teste o contrato. Com OpenAPI e logging correlacionado, clientes tratam falhas de forma previsível e a equipe investiga ocorrências sem criar um formato novo para cada endpoint.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita