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

Tratamento de Erros no Node.js

Atualizado em: 4 de setembro de 2026

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

O tratamento de erros no Node.js precisa diferenciar falhas esperadas, bugs, indisponibilidade de dependências e erros fatais do processo. Colocar tudo dentro de um try/catch genérico pode esconder problemas, gerar respostas inconsistentes e manter a aplicação funcionando em estado corrompido.

Uma estratégia sólida cria erros tipados, preserva a causa original, mapeia falhas nas bordas, registra contexto seguro e decide quando repetir, responder, degradar ou encerrar o processo.

Neste guia, você aprenderá erros operacionais e de programação, classes customizadas, cause, promises, Express, HTTP Problem Details, filas, logs, observabilidade, shutdown e testes.

Como erros funcionam no Node.js?

O Node.js usa objetos Error e mecanismos diferentes conforme a API:

  • exceções síncronas;
  • Promise rejeitada;
  • callback com primeiro argumento de erro;
  • evento error em streams e EventEmitter;
  • códigos de saída do processo;
  • erros do sistema com propriedades como code.

A documentação oficial apresenta os erros no Node.js.

Erro operacional versus bug

Erros operacionais podem ocorrer mesmo com código correto:

  • timeout;
  • arquivo ausente;
  • conexão recusada;
  • registro não encontrado;
  • entrada inválida;
  • conflito de concorrência.

Erros de programação indicam defeito:

  • acesso a propriedade de undefined;
  • argumento impossível;
  • invariante quebrada;
  • estado interno inconsistente;
  • Promise esquecida;
  • tipo incorreto em código interno.

A resposta para cada grupo é diferente.

Classe base

export class ApplicationError extends Error {
  constructor(
    message: string,
    readonly code: string,
    readonly details?: unknown,
    options?: ErrorOptions
  ) {
    super(message, options);
    this.name = new.target.name;
  }
}

O code é estável para decisões. A mensagem pode mudar.

Erros específicos

export class OrderNotFoundError
  extends ApplicationError {
  constructor(orderId: string) {
    super(
      'Pedido não encontrado',
      'ORDER_NOT_FOUND',
      { orderId }
    );
  }
}

export class OrderConflictError
  extends ApplicationError {
  constructor(orderId: string) {
    super(
      'O pedido foi alterado por outra operação',
      'ORDER_CONFLICT',
      { orderId }
    );
  }
}

Não crie classes apenas para repetir mensagens. O tipo precisa orientar tratamento.

Error cause

try {
  await provider.call(input);
} catch (error) {
  throw new PaymentProviderError(
    'Falha ao autorizar pagamento',
    { cause: error }
  );
}

A propriedade cause preserva a cadeia sem concatenar stacks manualmente.

Não exponha a causa ao cliente

O log interno pode incluir a cadeia. A resposta pública não deve revelar host, SQL, token ou stack.

Try/catch assíncrono

try {
  const result = await operation();
  return result;
} catch (error) {
  handle(error);
}

Sem await, o catch não captura a rejeição retornada:

try {
  return operation();
} catch {
  // não captura rejeição futura
}

Não capture para relançar igual

try {
  return await operation();
} catch (error) {
  throw error;
}

Esse bloco não agrega valor. Capture para adicionar contexto, traduzir ou limpar recursos.

Finally

const client = await pool.connect();

try {
  return await execute(client);
} finally {
  client.release();
}

finally é ideal para liberar conexão, arquivo ou lock.

Não retorne em finally

Um return no finally pode substituir o resultado ou esconder uma exceção.

Promises paralelas

Promise.all rejeita quando uma falha:

const [user, permissions] = await Promise.all([
  loadUser(id),
  loadPermissions(id)
]);

As outras operações não são automaticamente canceladas. Use AbortSignal quando necessário.

Promise.allSettled

Use quando todas as respostas são necessárias:

const results = await Promise.allSettled(tasks);

Depois trate cada status. Não ignore silenciosamente itens rejeitados.

Erros em callbacks

fs.readFile(path, (error, data) => {
  if (error) {
    return callback(error);
  }

  callback(null, data);
});

Retorne após o erro para evitar callback duplo.

Streams

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

Um EventEmitter sem listener de error pode encerrar o processo.

Pipeline

import { pipeline } from 'node:stream/promises';

await pipeline(source, transform, destination);

A versão Promise facilita captura e destrói streams envolvidos em falha.

Erros do sistema

try {
  await fs.readFile(path);
} catch (error) {
  if (isNodeError(error) && error.code === 'ENOENT') {
    return null;
  }

  throw error;
}

Não classifique por texto da mensagem. Use code.

Type guard

function isNodeError(
  error: unknown
): error is NodeJS.ErrnoException {
  return error instanceof Error
    && 'code' in error;
}

Em catch, trate o valor como unknown.

Normalização

function toError(value: unknown): Error {
  if (value instanceof Error) return value;

  return new Error(
    typeof value === 'string'
      ? value
      : 'Non-Error value thrown',
    { cause: value }
  );
}

JavaScript permite throw 'erro'. Normalize na borda.

Result Pattern

Falhas esperadas podem ser retornadas como Result em vez de exceção. Consulte Result Pattern no Node.js.

Quando usar Result

  • validação normal;
  • conflito esperado;
  • parsing;
  • regra de negócio negativa;
  • ausência tratável.

Quando lançar

  • contrato interno violado;
  • dependência indisponível;
  • configuração inválida;
  • bug;
  • falha que interrompe o fluxo atual.

Service Layer

Casos de uso podem lançar erros semânticos ou retornar Result. Controllers e consumidores mapeiam para o protocolo. Consulte Service Layer no Node.js.

Middleware do Express

app.use((error, req, res, next) => {
  const normalized = toError(error);

  if (normalized instanceof OrderNotFoundError) {
    return res.status(404).json({
      code: normalized.code,
      message: normalized.message
    });
  }

  logger.error({
    err: normalized,
    requestId: req.id
  }, 'Erro não tratado');

  res.status(500).json({
    code: 'INTERNAL_ERROR',
    message: 'Erro interno'
  });
});

O middleware deve ficar depois das rotas.

Express 5

Handlers async que rejeitam são encaminhados ao middleware de erro. Consulte Express 5: Erros Assíncronos.

HTTP Problem Details

A RFC 9457 define um formato padronizado, descrito em Problem Details for HTTP APIs.

{
  "type": "https://api.example.com/problems/order-conflict",
  "title": "Conflito no pedido",
  "status": 409,
  "detail": "O pedido foi alterado",
  "instance": "/orders/123",
  "code": "ORDER_CONFLICT"
}

Mensagens públicas

Mensagens devem ser úteis e seguras. Não retorne:

  • stack;
  • consulta SQL;
  • caminho interno;
  • nome de servidor;
  • credenciais;
  • payload completo;
  • mensagem bruta do fornecedor.

Status HTTP

  • 400: formato inválido;
  • 401: autenticação ausente ou inválida;
  • 403: sem autorização;
  • 404: recurso não encontrado;
  • 409: conflito de estado;
  • 412: precondição falhou;
  • 422: dados semanticamente inválidos;
  • 429: limite excedido;
  • 500: erro inesperado;
  • 502 ou 503: dependência indisponível;
  • 504: timeout upstream.

Mantenha política consistente.

Erros de validação

return res.status(422).json({
  code: 'VALIDATION_ERROR',
  errors: [
    { field: 'email', code: 'INVALID_EMAIL' }
  ]
});

Não retorne mensagens de biblioteca sem normalização.

PostgreSQL

Traduza códigos conhecidos:

  • 23505: unique violation;
  • 23503: foreign key;
  • 40001: serialization failure;
  • 40P01: deadlock.

Não exponha esses códigos ao cliente. Consulte Transações PostgreSQL no Node.js.

Retries

Repita apenas falhas transitórias e operações idempotentes:

if (isRetryable(error) && attempt < maxAttempts) {
  await delay(backoffWithJitter(attempt));
  continue;
}

Consulte Retry com Backoff no Node.js.

Timeouts

Timeout deve produzir erro próprio:

class DependencyTimeoutError extends ApplicationError {
  constructor(service: string, options?: ErrorOptions) {
    super(
      'Dependência excedeu o tempo limite',
      'DEPENDENCY_TIMEOUT',
      { service },
      options
    );
  }
}

AbortSignal

const signal = AbortSignal.timeout(3000);
await fetch(url, { signal });

Diferencie cancelamento solicitado de timeout quando a resposta operacional precisar disso.

Circuit Breaker

Falhas repetidas podem abrir o circuito e retornar erro de indisponibilidade rapidamente. Consulte Circuit Breaker no Node.js.

Filas

Um consumidor decide:

  • ack para sucesso;
  • dead-letter para mensagem inválida;
  • retry para falha transitória;
  • alarme para bug inesperado.

Não repita permanentemente uma mensagem que nunca será válida.

Idempotência

Retries de mensagens e HTTP podem duplicar efeitos. Consulte Idempotência em APIs Node.js.

Logs estruturados

logger.error({
  err: error,
  requestId,
  operation: 'approve_order',
  orderId
}, 'Falha ao aprovar pedido');

Use Pino ou Winston com redaction. Consulte Logs com Pino no Node.js.

Não registre duas vezes

Se cada camada registra o mesmo erro, haverá duplicidade. Uma convenção comum:

  • camada interna adiciona contexto e relança;
  • borda registra uma vez;
  • erros esperados são métricas ou logs leves.

Stack trace

Stacks são úteis internamente, mas podem ficar grandes. Preserve em erros inesperados e evite enviá-las ao usuário.

Sentry

Capture bugs e falhas inesperadas, não cada 404 ou validação. Consulte Sentry no Node.js.

Métricas

Meça erros por operação e tipo controlado:

application_errors_total{
  operation="approve_order",
  type="dependency_timeout"
}

Não use mensagem como label.

Unhandled rejection

Promises rejeitadas sem tratamento indicam bug. Registre e inicie shutdown controlado quando o estado puder estar inconsistente.

process.on('unhandledRejection', reason => {
  fatalErrorHandler(toError(reason));
});

Uncaught exception

process.on('uncaughtException', error => {
  fatalErrorHandler(error);
});

Não continue atendendo normalmente após exceção não capturada.

Fatal error handler

let shuttingDown = false;

async function fatalErrorHandler(error: Error) {
  if (shuttingDown) return;
  shuttingDown = true;

  logger.fatal({ err: error }, 'Erro fatal');
  process.exitCode = 1;

  await shutdown({ timeoutMs: 10000 });
}

Consulte Graceful Shutdown no Node.js.

Não chame process.exit imediatamente

Isso pode perder logs e interromper requests. Defina exitCode, feche recursos e use prazo máximo.

Domínios e invariantes

Uma invariante quebrada por dados internos pode ser fatal para a operação atual. Value Objects e entidades ajudam a impedir estados inválidos.

Consulte Value Objects no Node.js.

Erros e segurança

Mensagens diferentes para usuário existente e inexistente podem permitir enumeração. Em autenticação, use respostas neutras quando necessário.

Correlation ID

Inclua um ID na resposta e nos logs:

{
  "code": "INTERNAL_ERROR",
  "requestId": "req-..."
}

O usuário pode informar o ID ao suporte sem receber detalhes internos.

Testes unitários

test('mapeia OrderNotFound para 404', async () => {
  const response = await request(app)
    .get('/orders/missing');

  assert.equal(response.status, 404);
  assert.equal(
    response.body.code,
    'ORDER_NOT_FOUND'
  );
});

Teste de causa

Simule erro do provider e confirme que o erro traduzido preserva cause no log, mas não na resposta.

Teste de timeout

Use um servidor falso lento. Confirme status, código, retry e cancelamento.

Teste de unhandled rejection

Teste o fatal handler como função injetável, sem derrubar o processo do runner. Confirme que inicia shutdown e define código de saída.

Fault injection

Provoque falhas em banco, cache, fila e filesystem. Verifique limpeza de recursos, rollback e logs.

Erros comuns

  • Catch vazio: falha desaparece.
  • Mensagem como decisão: tratamento quebra após tradução.
  • Erro genérico: não há semântica.
  • Stack ao cliente: informação interna vaza.
  • Log em toda camada: duplicidade aumenta.
  • Retry de bug: carga e dano crescem.
  • Continuar após fatal: estado pode estar corrompido.
  • Sem timeout: operações ficam penduradas.

Boas práticas

  • Trate catch como unknown.
  • Use códigos estáveis.
  • Preserve cause.
  • Diferencie esperado e inesperado.
  • Mapeie erros nas bordas.
  • Não exponha detalhes internos.
  • Use timeouts e cancelamento.
  • Repita apenas falhas transitórias.
  • Registre uma vez com contexto.
  • Encerre após erros fatais.

Conclusão

O tratamento de erros no Node.js é uma política arquitetural, não apenas blocos try/catch. Erros tipados, códigos estáveis, causas preservadas e mapeamento nas bordas tornam falhas previsíveis.

Com Result para caminhos esperados, exceções para falhas inesperadas, logs estruturados, timeouts e shutdown controlado, a aplicação responde com segurança e não esconde bugs críticos.

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