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.