Programação II Tratamento de erros Middlewares de erro no Express

Tratamento de erros e middlewares de erro no Express

Nesta página você vai aprender, passo a passo, como organizar o tratamento de erros na sua API Express usando middlewares de erro, de forma clara e padronizada. O material serve tanto para estudo autônomo quanto como roteiro de apresentação da aula.

1. Por que se preocupar com tratamento de erros?

Em uma API simples é comum escrever try/catch em cada rota e mandar mensagens diferentes em cada resposta. Mas, conforme a aplicação cresce, isso se torna difícil de manter e de testar.

O Express oferece um recurso específico para isso: o middleware de erro global, que centraliza o tratamento de erros em um único lugar, deixando as rotas mais limpas.

Sintomas de que o tratamento de erros está ruim:

  • Cada rota devolve mensagens diferentes para o mesmo tipo de erro.
  • Alguns erros geram resposta 200 com mensagem de erro no body.
  • Muito código repetido de try/catch em todas as rotas.
  • É difícil encontrar onde logar um erro para debug.

O objetivo desta aula é sair de um tratamento de erros “caseiro” para um formato mais organizado, usando de forma correta o next(err) e um middleware de erro registrado no final da aplicação Express.

2. Conceitos básicos de tratamento de erros no Express

Em um app Express, o fluxo padrão de uma requisição passa por vários middlewares e rotas. Se algo dá errado no meio do caminho, podemos:

  • Lançar uma exceção com throw new Error(...) dentro de um try.
  • Passar o erro para o próximo middleware usando next(err).
  • Deixar que o middleware de erro trate e devolva a resposta padrão.
Middleware comum
Função com assinatura (req, res, next).
Middleware de erro
Função com assinatura (err, req, res, next) e registrada no final da cadeia.
next(err)
Interrompe o fluxo normal e pula para o próximo middleware de erro disponível.
⚠️
Pense no middleware de erro como uma central de atendimento: as rotas apenas avisam “aconteceu um problema” e a central decide qual mensagem e qual status HTTP devem ser enviados ao cliente.

3. Criando um middleware de erro no Express

Um middleware de erro é uma função com quatro parâmetros: (err, req, res, next). Ele deve ser registrado depois de todas as rotas e outros middlewares.

3.1 Estrutura básica

const express = require('express');
const app = express();

app.use(express.json());

// Exemplo de rota que pode gerar erro
app.get('/simular-erro', (req, res, next) => {
  try {
    // algo deu errado aqui...
    throw new Error('Falha ao buscar dados');
  } catch (erro) {
    // encaminha o erro para o middleware de erro
    next(erro);
  }
});

// Middleware de erro (DEVE ser o último)
app.use((err, req, res, next) => {
  console.error('Erro capturado:', err.message);

  res.status(500).json({
    erro: 'Erro interno no servidor',
    mensagem: err.message
  });
});

app.listen(3000, () => {
  console.log('Servidor rodando em http://localhost:3000');
});
O Express só reconhece um middleware como “de erro” se ele tiver quatro parâmetros na assinatura (mesmo que você não use o next).

3.2 Encaminhando erros com next(err)

Dentro das rotas e middlewares, você pode chamar next(err) para avisar ao Express que houve um problema. Ele então pula os demais middlewares comuns e vai direto para o middleware de erro global.

app.get('/usuarios/:id', async (req, res, next) => {
  try {
    const id = Number(req.params.id);
    if (Number.isNaN(id)) {
      const erro = new Error('ID inválido');
      erro.status = 400;
      return next(erro);
    }

    const usuario = await buscarUsuarioNoBanco(id);
    if (!usuario) {
      const erro = new Error('Usuário não encontrado');
      erro.status = 404;
      return next(erro);
    }

    res.json(usuario);
  } catch (erro) {
    next(erro); // erros inesperados (banco, código, etc.)
  }
});

4. Padronizando respostas de erro

Em vez de devolver cada erro com um formato diferente, vamos padronizar a resposta. Uma forma simples é sempre retornar um objeto com campos como erro e detalhes.

4.1 Middleware de erro com status dinâmico

app.use((err, req, res, next) => {
  console.error(err);

  const status = err.status || 500;
  const mensagem = err.message || 'Erro interno no servidor';

  res.status(status).json({
    erro: mensagem,
    caminho: req.path,
  });
});

Boas ideias para incluir na resposta de erro:

  • erro: mensagem legível para o cliente.
  • caminho: rota em que o erro ocorreu.
  • (Opcional) código interno de erro, útil para o front-end.

4.2 Criando uma classe de erro da aplicação (opcional)

class AppError extends Error {
  constructor(mensagem, status) {
    super(mensagem);
    this.status = status;
  }
}

// Em uma rota:
app.get('/produtos/:id', async (req, res, next) => {
  try {
    const id = Number(req.params.id);
    if (Number.isNaN(id)) {
      return next(new AppError('ID de produto inválido', 400));
    }

    const produto = await buscarProduto(id);
    if (!produto) {
      return next(new AppError('Produto não encontrado', 404));
    }

    res.json(produto);
  } catch (erro) {
    next(erro);
  }
});

5. Boas práticas de tratamento de erros

Alguns cuidados simples ajudam a manter o tratamento de erros limpo e consistente em toda a aplicação.

5.1 Pontos principais

  • Registrar o middleware de erro depois de todas as rotas.
  • Usar status codes corretos (400, 404, 500, etc.).
  • Evitar enviar detalhes sensíveis de erros para o cliente (como stack completa) em produção.
  • Usar mensagens claras e objetivas para o front-end.
  • Logar erros de forma centralizada (console hoje, serviço de log no futuro).

Antes

try/catch com res.send('deu erro') espalhado por todas as rotas.

Depois

Rotas chamam next(err) e o middleware de erro decide status e formato.

6. Como isso será usado no projeto do semestre

No projeto de Programação II, sua aplicação terá diversas rotas, formulários e chamadas à API. Erros vão acontecer: dados inválidos, recursos inexistentes, problemas com o banco, etc.

6.1 No back-end

  • Você terá um middleware de erro global cuidando das respostas em JSON.
  • Rotas vão usar next(err) para comunicar problemas ao middleware.
  • Você poderá diferenciar erros esperados (validação, não encontrado) de erros inesperados.

6.2 No front-end

  • Funções de consumo de API (fetch/axios) vão ler status e corpo de erro para mostrar mensagens adequadas ao usuário.
  • Formulários vão exibir feedback claro (por exemplo, avisar campos obrigatórios ou mostrar que um item não foi encontrado).
  • O README do projeto deve documentar brevemente como os erros são tratados (formato do JSON de erro e exemplos de respostas).

Um bom tratamento de erros melhora a experiência de quem usa o sistema, facilita o debug e deixa o código mais organizado. É um dos diferenciais de um projeto bem acabado.