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

Tratamento de Erros no Node.js

Atualizado em: 4 de outubro de 2026

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

Tratamento de erros em Node.js envolve representar falhas, classificá-las, preservar contexto, devolver respostas seguras e decidir quando a aplicação pode continuar. Um try/catch isolado não resolve erros assíncronos, falhas de dependências, validação, concorrência e estado inconsistente.

Uma estratégia clara diferencia erros operacionais esperados de bugs de programação. Erros operacionais podem ser tratados e comunicados. Bugs podem exigir encerramento controlado e reinício do processo.

Erros operacionais

Exemplos:

  • entrada inválida;
  • recurso não encontrado;
  • permissão negada;
  • timeout;
  • serviço externo indisponível;
  • conflito de versão;
  • rate limit;
  • arquivo inexistente.

Esses casos fazem parte do contrato e devem produzir respostas conhecidas.

Erros de programação

Exemplos:

  • acesso a propriedade de undefined;
  • invariante quebrada;
  • dupla execução de callback;
  • estado impossível;
  • tipo inesperado após validação;
  • rejeição não tratada.

Não esconda bugs retornando um valor padrão silenciosamente.

Classe de erro

export class AppError extends Error {
  constructor(message, {
    code,
    statusCode = 500,
    details,
    cause,
    operational = true,
  } = {}) {
    super(message, { cause });
    this.name = this.constructor.name;
    this.code = code;
    this.statusCode = statusCode;
    this.details = details;
    this.operational = operational;
  }
}

Use códigos estáveis para consumidores. A mensagem pode mudar e não deve ser usada em lógica.

Erros específicos

export class NotFoundError extends AppError {
  constructor(resource, id) {
    super(`${resource} não encontrado`, {
      code: 'resource_not_found',
      statusCode: 404,
      details: { resource, id },
    });
  }
}

export class ValidationError extends AppError {
  constructor(issues) {
    super('Dados inválidos', {
      code: 'validation_failed',
      statusCode: 400,
      details: { issues },
    });
  }
}

Não crie dezenas de classes sem benefício. Um conjunto pequeno e consistente costuma ser suficiente.

Error cause

try {
  await repository.save(order);
} catch (error) {
  throw new AppError('Falha ao salvar pedido', {
    code: 'order_persistence_failed',
    statusCode: 503,
    cause: error,
  });
}

cause preserva a falha original sem concatenar mensagens e perder stack.

Não capture para relançar igual

// Desnecessário
try {
  return await operation();
} catch (error) {
  throw error;
}

Capture quando vai adicionar contexto, mapear, limpar recursos ou aplicar política.

Finally

const client = await pool.connect();
try {
  return await execute(client);
} finally {
  client.release();
}

finally deve liberar recursos. Evite lançar um novo erro que esconda a falha principal durante limpeza.

Promises

await operation().catch((error) => {
  logger.warn({ err: error }, 'Operação opcional falhou');
});

Use catch local apenas quando a falha realmente é opcional ou será transformada.

Promise.all

Promise.all rejeita na primeira falha observada, mas as demais operações continuam. Se precisa cancelar:

const controller = new AbortController();

try {
  return await Promise.all([
    loadA({ signal: controller.signal }),
    loadB({ signal: controller.signal }),
  ]);
} catch (error) {
  controller.abort(error);
  throw error;
}

Promise.allSettled

Use quando todas as operações devem concluir e falhas individuais fazem parte do resultado:

const results = await Promise.allSettled(tasks);

Não use para ignorar erros sem analisá-los.

Callbacks

APIs antigas seguem error-first callback:

operation((error, result) => {
  if (error) {
    handle(error);
    return;
  }
  use(result);
});

Retorne após tratar o erro para evitar execução dupla.

EventEmitter

Streams e emitters podem emitir error. Sem listener, o processo pode encerrar:

stream.on('error', (error) => {
  logger.error({ err: error }, 'Stream falhou');
});

Prefira pipeline para streams conectadas.

Express

Um middleware final centraliza resposta:

app.use((error, req, res, next) => {
  const statusCode = error.statusCode || 500;
  const requestId = req.context?.requestId;

  logger.error({ err: error, requestId }, 'Requisição falhou');

  res.status(statusCode).json({
    error: error.code || 'internal_error',
    message: statusCode < 500
      ? error.message
      : 'Erro interno',
    requestId,
  });
});

Não devolva stack ou mensagem interna em 500.

Handlers assíncronos

Garanta que rejeições cheguem ao middleware conforme a versão do framework. Um wrapper pode ser necessário:

const asyncHandler = (handler) => (req, res, next) => {
  Promise.resolve(handler(req, res, next)).catch(next);
};

Resposta já iniciada

Se headers foram enviados, não é possível trocar por JSON de erro. Para streaming, destrua a conexão ou finalize conforme protocolo.

if (res.headersSent) {
  next(error);
  return;
}

Validação

Valide entrada na borda e transforme erros da biblioteca em formato estável. Não exponha detalhes de schema, SQL ou stack.

Mapeando erros externos

Um 404 de uma dependência não precisa virar 404 da sua API. Mapeie pela semântica:

  • dependência indisponível: 503;
  • timeout externo: 504;
  • entrada do usuário inválida: 400;
  • conflito: 409;
  • precondição: 412;
  • rate limit: 429.

Timeout

Timeout é uma categoria específica. Inclua dependência e operação nos logs, mas devolva mensagem genérica ao cliente.

Retries

Classifique se o erro é transitório e se a operação é idempotente. Não coloque retry genérico no middleware de erros.

Circuit breaker

Quando o breaker está aberto, produza erro operacional conhecido e fallback quando seguro.

Erros de domínio

Uma regra de negócio pode lançar:

throw new AppError('Estoque insuficiente', {
  code: 'insufficient_stock',
  statusCode: 409,
});

Esse erro não deve ser registrado como falha interna grave.

Códigos estáveis

Clientes devem usar error ou code, não comparar texto traduzido.

Problem Details

APIs podem adotar um formato padronizado:

{
  "type": "https://example.com/problems/validation",
  "title": "Dados inválidos",
  "status": 400,
  "detail": "Dois campos precisam ser corrigidos.",
  "instance": "/orders/123"
}

Não exponha URLs internas ou dados sensíveis.

Request ID

Inclua um identificador na resposta de erro e nos logs. Isso ajuda suporte sem mostrar stack.

Logs

Registre o erro uma vez na camada responsável. Evite registrar em repository, service, controller e middleware, produzindo quatro cópias.

Nível de log

  • 400 esperado: info ou warn;
  • 404 comum: info ou sem log;
  • 429: warn agregado;
  • 503 externo: warn ou error conforme impacto;
  • bug: error ou fatal.

Redaction

Stacks e mensagens de drivers podem conter SQL, caminhos ou dados. Redija e limite. Não registre payload completo.

Observabilidade

Meça erros por:

  • código estável;
  • operação;
  • status HTTP;
  • dependência;
  • versão;
  • retry;
  • fallback;
  • origem operacional ou bug.

Não use mensagem como label.

Tracing

Marque span como erro e grave exceção sanitizada. Preserve trace ID no log.

Uncaught exception

Uma exceção não capturada indica que o processo escapou da estratégia normal. Registre de forma mínima, pare de aceitar tráfego e encerre. Não continue operando em estado desconhecido.

Unhandled rejection

Rejeições não tratadas devem ser consideradas bug. Corrija a origem, aplique política de processo e use supervisor externo.

Graceful shutdown

Em falha fatal:

  1. marcar not-ready;
  2. parar entradas;
  3. drenar dentro de prazo;
  4. fechar recursos;
  5. flush de telemetria;
  6. encerrar com código não zero.

Não usar process.exit em qualquer catch

Erros operacionais de uma requisição não devem derrubar o processo. Reserve encerramento para estado inconsistente ou política fatal.

Testes

Teste mapeamento, status, corpo seguro, request ID, cause, limpeza, resposta iniciada, timeout, dependência e bug.

Chaos testing

Simule indisponibilidade de banco, reset, timeout e disco cheio. Confirme que a aplicação falha de forma controlada.

Erros comuns

  • capturar e ignorar;
  • devolver stack;
  • comparar mensagem;
  • registrar o mesmo erro várias vezes;
  • usar 500 para tudo;
  • fazer retry no middleware;
  • não liberar recursos;
  • continuar após bug fatal;
  • process.exit em erro operacional;
  • não preservar cause.

Fluxo recomendado

Defina erros operacionais com códigos estáveis, centralize tradução HTTP e preserve cause. Registre uma vez, devolva resposta segura e encerre em falhas fatais. Combine com Pino no Node.js, Graceful Shutdown, AbortController e Retry com Backoff.

Consulte a documentação oficial de erros do Node.js e a especificação Problem Details for HTTP APIs.

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