Respostas de Erro Estruturadas
Sem tratamento de erros, o Express retorna HTML genérico quando algo dá errado — inútil para um cliente que espera JSON. Vamos definir um formato consistente para a API:
{
"error": {
"code": "ROUTE_NOT_FOUND",
"message": "A rota solicitada não existe."
}
}
O campo code é um identificador legível por máquina (o frontend pode usar para
decidir o que exibir). O message é uma descrição legível por humano, útil para
debug.
Middleware de tratamento de erros
No Express, um middleware de erro é uma função com quatro parâmetros — err,
req, res, next. O Express identifica o error handler pela aridade da
função:
import { Request, Response, NextFunction } from "express";
interface AppError extends Error {
statusCode?: number;
code?: string;
}
export function errorHandler(
err: AppError,
req: Request,
res: Response,
next: NextFunction,
) {
console.error(err);
const statusCode = err.statusCode ?? 500;
const code = err.code ?? "INTERNAL_ERROR";
const message = err.message ?? "Erro interno do servidor.";
res.status(statusCode).json({
error: { code, message },
});
}
Código 5-14: Middleware que captura erros e responde com JSON estruturado.
Quando o controller chama next(err), o Express pula todos os middlewares
normais e vai direto para esse handler. O erro é logado no console do servidor
(para o desenvolvedor) e uma resposta JSON é enviada ao cliente.
Rota inexistente
Uma requisição para uma rota que não existe precisa retornar 404 com o nosso
formato de erro. Adicione este middleware no app.ts, depois das rotas e
antes do errorHandler:
app.use(eventsRoutes);
app.use((req: Request, res: Response, next: NextFunction) => {
const err: AppError = new Error("A rota solicitada não existe.");
err.statusCode = 404;
err.code = "ROUTE_NOT_FOUND";
next(err);
});
app.use(errorHandler);
Código 5-15: Middleware de 404 posicionado entre as rotas e o error handler no app.ts.
Testando:
$ curl -s http://localhost:3000/api/naoexiste | jq
{
"error": {
"code": "ROUTE_NOT_FOUND",
"message": "A rota solicitada não existe."
}
}
E se o banco de dados estiver fora do ar? O pool.query lança um erro com
code = "ECONNREFUSED", que o middleware captura:
$ curl -s http://localhost:3000/api/events | jq
{
"error": {
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 127.0.0.1:5432"
}
}
Repare que o code aqui é o código interno do Node.js, não um código de
aplicação. E a mensagem expõe o IP e a porta do banco — em produção, isso seria
um problema de segurança. No ambiente de desenvolvimento, essa transparência
ajuda: você sabe imediatamente que o PostgreSQL não está rodando. A
distinção entre erros públicos e internos vem num capítulo
posterior1.
-
O error handler vai precisar distinguir entre erros que o cliente pode ver (como “rota não encontrada”) e erros internos (como falha de conexão com o banco). Erros internos devem retornar uma mensagem genérica ao cliente e logar os detalhes apenas no servidor. ↩