Todo mundo projeta as suas aplicações para que funcionem, certo?
Mas elas funcionam sempre?
É claro que não!
Aí mora uma falha de design de software muito comum: raramente os programadores pensem na manipulação dos erros das aplicações nas quais trabalham, o que gera dores de cabeça enormes no médio e longo prazo, quando o sistema já está em produção e precisamos fazer troubleshooting, mudar tecnologia de logging ou até mesmo manipular os erros de forma mais eficiente.
Que tal aprender a fazer error handling em Express de forma elegante e que te permita evoluir a sua aplicação com qualidade ao longo do tempo?
Esse é o assunto do tutorial de hoje!
Atenção: este tutorial é para quem já sabe programar ao menos o básico de Node.js com Express.
#1 – Voltando ao Básico
O primeiro passo é obviamente revisitar o assunto manipulação de erro em JavaScript. Basicamente quando queremos capturar um erro que possa ser lançado em certo bloco de código, usamos try/catch, certo?
try{
//seu código aqui
}
catch(error){
//seu tratamento de erro aqui
}
Se um erro acontecer dentro do bloco try, o fluxo será redirecionado para o bloco catch onde você terá acesso a um objeto error com os dados do erro, permitindo que você trate, registre e devolva ao usuário uma resposta mais amigável.
O grande problema do try/catch é…bem, que você tem de escrevê-lo em todos os pontos da sua aplicação onde não quer que um erro possa estourar e com isso prejudicar a experiência do usuário.
Não fosse o bastante, se você estiver com uma web API em Node.js feita com Express, se você tiver o estouro de um erro sem o devido tratamento, o seu webserver pode cair! Sim, porque erros não tratados no Event Loop simplesmente podem derrubar o Event Loop! E isso não é uma característica exclusiva do Node.js ou do JavaScript, programas de computador em geral são encerrados automaticamente se der uma exceção não tratada no seu fluxo principal!
const express = require('express');
const app = express();
app.get('/teste1', (req, res, next) => {
res.send('teste1');
})
app.get('/teste2', (req, res, next) => {
try {
throw new Error('teste2 deu erro');
} catch (error) {
console.log(error);
res.sendStatus(500);
}
})
app.get('/teste3', (req, res, next) => {
throw new Error('teste3 deu erro');
})
app.listen(3000, () => {
console.log('Server running at 3000');
})
Não esqueça de instalar a dependência do express antes de rodar este projeto com “npm install”.
Teste as 3 rotas usando Postman ou mesmo no seu navegador. Consegue perceber a diferença entre as 3? Em especial a 2 e 3, pois na 2 conseguimos tratar o erro e dar algum destino diferente para a requisição, enquanto que na 3 estoura um erro grosseiro e que não temos controle, inclusive expondo nosso servidor ao requisitante.
Jamais deixe suas rotas desta forma!
#2 – Error Middleware
Uma forma de fazer o gerenciamento de erros de forma mais profissional é centralizando-o através de um error middleware.
O Express é todo organizado através de middlewares de processamento e existe um middleware especial que chamamos de Error Middleware que serve como destino default para todos os erros gerados e não tratados no event loop.
Enquanto que um middleware normal possui 3 parâmetros (req, res e next), o Error Middleware é criado passando um parâmetro a mais, que na verdade deve ser o primeiro: error. Além disso, ele deve ser o último middleware na sua cadeia de processamento.
app.get('/teste3', (req, res, next) => {
throw new Error('teste3 deu erro');
})
app.use((error, req, res, next) => {
console.log('error middleware');
res.sendStatus(500);
})
No exemplo acima, criei o error middleware logo após a última rota. Experimente subir novamente sua aplicação e testar a rota 3. Verá que apesar de ela não possuir try/catch, seu fluxo será redirecionado para o error middleware automaticamente quando o erro estourar, simplificando bastante o tratamento de erros pois agora ele estará centralizado.
Ainda assim, nas rotas que você desejar ainda ter um try/catch personalizado, você pode mantê-lo e redirecionar para o error middleware opcionalmente, com a função next, passando o erro como único argumento.
app.get('/teste2', (req, res, next) => {
try {
throw new Error('teste2 deu erro');
} catch (error) {
console.log(error);
next(error);
}
})
A partir da v5 do Express você também tem suporte a captura de erros assíncronos, então pode testar o código abaixo que também vai funcionar.
app.get('/teste3', async (req, res, next) => {
throw new Error('teste3 deu erro');
})
Como próximos passos, que eu não vou abordar hoje pois este tutorial já está se tornando extenso, você pode utilizar Custom Errors na sua aplicação para que o Error Middleware consiga identificar os tipos de erro e dar retornos apropriados.