Tratamento de erros no Express
1. Fundamentos do tratamento de erros no Express
1.1. Middleware de erro: assinatura e funcionamento
No Express, middlewares de erro são funções com quatro parâmetros — err, req, res, next. O primeiro parâmetro é obrigatoriamente o erro, diferentemente de middlewares comuns que possuem apenas três parâmetros.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Erro interno do servidor' });
});
1.2. Como o Express identifica e propaga erros
Quando você chama next(err) dentro de qualquer middleware ou rota, o Express pula todos os middlewares comuns seguintes e procura o próximo middleware de erro na cadeia.
app.get('/dados', (req, res, next) => {
const dados = obterDados();
if (!dados) {
const erro = new Error('Dados não encontrados');
erro.statusCode = 404;
next(erro); // Propaga para o middleware de erro
return;
}
res.json(dados);
});
1.3. Diferença entre middleware comum e middleware de erro
- Middleware comum: 3 parâmetros
(req, res, next)— processa requisições normais. - Middleware de erro: 4 parâmetros
(err, req, res, next)— captura erros propagados vianext(err)ou exceções lançadas.
Se um middleware comum lançar uma exceção não capturada, o Express automaticamente a encaminha para o middleware de erro.
2. Estratégias para capturar erros em rotas assíncronas
2.1. Try/catch manual em handlers async
app.get('/usuarios/:id', async (req, res, next) => {
try {
const usuario = await buscarUsuario(req.params.id);
res.json(usuario);
} catch (err) {
next(err);
}
});
2.2. Wrapper function para evitar repetição de try/catch
Crie uma função utilitária que envolve handlers assíncronos:
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/usuarios/:id', asyncHandler(async (req, res) => {
const usuario = await buscarUsuario(req.params.id);
res.json(usuario);
}));
2.3. Utilizando o pacote express-async-errors
Este pacote faz o Express capturar automaticamente rejeições de promises em rotas assíncronas:
import 'express-async-errors';
app.get('/usuarios/:id', async (req, res) => {
const usuario = await buscarUsuario(req.params.id); // Erros aqui são capturados automaticamente
res.json(usuario);
});
3. Criando uma classe de erro personalizada
3.1. Estendendo a classe Error nativa do JavaScript
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true; // Indica que é um erro operacional esperado
Error.captureStackTrace(this, this.constructor);
}
}
3.2. Adicionando propriedades: statusCode, isOperational
statusCode: código HTTP apropriado (400, 404, 500, etc.)isOperational: distingue erros operacionais (esperados) de erros de programação (inesperados)
3.3. Exemplo prático: AppError para erros de negócio
app.post('/transferencia', async (req, res, next) => {
try {
const { contaOrigem, contaDestino, valor } = req.body;
if (valor <= 0) {
throw new AppError('Valor da transferência deve ser positivo', 400);
}
const saldo = await obterSaldo(contaOrigem);
if (saldo < valor) {
throw new AppError('Saldo insuficiente', 400);
}
await realizarTransferencia(contaOrigem, contaDestino, valor);
res.json({ message: 'Transferência realizada com sucesso' });
} catch (err) {
next(err);
}
});
4. Middleware global de tratamento de erros
4.1. Estrutura do middleware de erro centralizado
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.message || 'Erro interno do servidor';
res.status(statusCode).json({
status: 'error',
statusCode,
message
});
});
4.2. Como diferenciar erros operacionais de erros de programação
app.use((err, req, res, next) => {
if (err.isOperational) {
// Erro esperado: retorna mensagem amigável
res.status(err.statusCode).json({
status: 'error',
message: err.message
});
} else {
// Erro de programação: log detalhado, resposta genérica
console.error('ERRO NÃO OPERACIONAL:', err);
res.status(500).json({
status: 'error',
message: 'Algo deu errado. Tente novamente mais tarde.'
});
}
});
4.3. Envio de respostas JSON padronizadas para o cliente
app.use((err, req, res, next) => {
const response = {
success: false,
error: {
code: err.statusCode || 500,
message: err.message || 'Internal Server Error'
}
};
// Em desenvolvimento, inclui stack trace
if (process.env.NODE_ENV === 'development') {
response.error.stack = err.stack;
}
res.status(response.error.code).json(response);
});
5. Tratamento de erros específicos do Express
5.1. Erro 404 para rotas não encontradas
// Deve ser o último middleware antes do middleware de erro
app.use((req, res, next) => {
next(new AppError(`Rota ${req.originalUrl} não encontrada`, 404));
});
5.2. Erros de validação de entrada (ex: Joi, express-validator)
const { validationResult } = require('express-validator');
app.post('/usuario', [
body('email').isEmail().withMessage('Email inválido'),
body('senha').isLength({ min: 6 }).withMessage('Senha deve ter no mínimo 6 caracteres')
], (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(new AppError(errors.array().map(e => e.msg).join(', '), 400));
}
// Processa a requisição...
});
5.3. Erros de parsing (JSON malformado, payload muito grande)
// JSON malformado
app.use((err, req, res, next) => {
if (err.type === 'entity.parse.failed') {
return res.status(400).json({ message: 'JSON inválido no corpo da requisição' });
}
next(err);
});
// Payload muito grande (limite padrão: 100kb)
app.use(express.json({ limit: '10mb' }));
app.use((err, req, res, next) => {
if (err.type === 'entity.too.large') {
return res.status(413).json({ message: 'Payload muito grande' });
}
next(err);
});
6. Integração com logging e monitoramento
6.1. Logging estruturado com Winston no middleware de erro
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log' }),
new winston.transports.Console({ format: winston.format.simple() })
]
});
app.use((err, req, res, next) => {
logger.error({
message: err.message,
stack: err.stack,
method: req.method,
url: req.originalUrl,
timestamp: new Date().toISOString()
});
next(err);
});
6.2. Diferença entre log de desenvolvimento e produção
if (process.env.NODE_ENV === 'production') {
logger.add(new winston.transports.File({ filename: 'errors.log' }));
} else {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
6.3. Envio de erros para serviços externos (Sentry)
const Sentry = require('@sentry/node');
Sentry.init({ dsn: process.env.SENTRY_DSN });
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());
app.use((err, req, res, next) => {
Sentry.captureException(err);
res.status(500).json({ message: 'Erro reportado à equipe de desenvolvimento' });
});
7. Tratamento de erros no frontend React
7.1. Interceptando respostas de erro da API com Axios interceptors
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3000/api'
});
api.interceptors.response.use(
response => response,
error => {
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 400:
console.error('Erro de validação:', data.message);
break;
case 401:
console.error('Não autorizado');
// Redirecionar para login
break;
case 404:
console.error('Recurso não encontrado');
break;
case 500:
console.error('Erro interno do servidor');
break;
default:
console.error('Erro desconhecido');
}
}
return Promise.reject(error);
}
);
7.2. Exibindo mensagens de erro amigáveis ao usuário
import React, { useState } from 'react';
function LoginForm() {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
await api.post('/login', { email, senha });
// Sucesso: redirecionar
} catch (err) {
const message = err.response?.data?.message || 'Erro de conexão. Tente novamente.';
setError(message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error-message">{error}</div>}
{/* Campos do formulário */}
<button type="submit" disabled={loading}>
{loading ? 'Carregando...' : 'Entrar'}
</button>
</form>
);
}
7.3. Criando um componente de fallback com Error Boundaries
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Erro capturado pelo Error Boundary:', error, errorInfo);
// Enviar para serviço de monitoramento
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Algo deu errado</h2>
<p>Desculpe pelo inconveniente. Tente recarregar a página.</p>
<button onClick={() => window.location.reload()}>
Recarregar
</button>
</div>
);
}
return this.props.children;
}
}
// Uso:
function App() {
return (
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
);
}
Referências
- Documentação oficial do Express sobre tratamento de erros — Guia completo sobre middlewares de erro, propagação e boas práticas no Express.
- Express Async Errors no npm — Pacote que elimina a necessidade de try/catch manual em rotas assíncronas do Express.
- Winston: Logger para Node.js — Biblioteca de logging estruturado, ideal para registrar erros em produção.
- Sentry para Node.js — Documentação oficial de integração do Sentry para monitoramento de erros em aplicações Node.js.
- Error Boundaries no React — Documentação oficial do React sobre componentes de fallback para captura de erros de renderização.
- Axios Interceptors — Guia oficial sobre interceptors do Axios para tratamento centralizado de respostas de API.