Tratamento de erros no Express v5.0.0

Introdução

No desenvolvimento de aplicações, existem vários casos de uso que devem ser implementados durante uma sprint. Dentro dos casos de uso, existem dois tipos de fluxo: o fluxo principal em que o caso de uso executa sem exceções e o fluxo alternativo que pode ser a realização de outro procedimento ou o disparo de um erro (ver This is how to write the flow of use cases | by Chaitanya Joshi | Medium).

Todo o programador, já deve ter ouvido falar do try/catch para tratamento de erros. No entanto talvez nunca tenha refletido sobre as boas práticas do uso do try/catch.

Evitar o try catch

Deve-se evitar ao máximo o uso do try/catch. Por exemplo, imaginemos que estamos a implementar o acesso a uma base de dados. Por vezes, há erros comuns que são cometidos como fechar a conexão de uma base de dados que já foi fechada. Se executarmos esse comando, é lançada uma exceção indicando que a conexão já foi encerrada. Para isso, só temos de verificar se a conexão foi fechada e se não foi fechada, executar a operação (ver Best practices for exceptions - .NET | Microsoft Learn). Por isso, a melhor prática é evitar usar try/catch.

A desvantagem de usar try/catch é que o código fica muito mais confuso de ler. Existem várias formas de devolver um erro. Uma delas é devolver um objeto com o erro dentro, o pattern Result (ver Result Pattern. Photo by John Schnobrich on Unsplash | by Ben Witt | Medium).

Outra maneira é criar um middleware que captura os erros e devolva uma mensagem padrão.

Isso é possível em qualquer linguagem, mas em algum lugar o try catch terá de aparecer e provavelmente será na entrada do programa.

Middleware de tratamento de erro no Express V.5.X.X

Em sistemas orientados a serviços, é possível capturar os erros de forma padronizada. Tal acontece com a biblioteca Express do Node.js. Desta forma existe uma redução da quantidade de linhas de código escritas (ver Express 5 Brings Built-in Promise Support for Error Handling - DEV Community).

Este middleware funciona tanto para funções assíncronas e para funções síncronas, mas só foi adicionada esta funcionalidade na versão 5 do Express. Na versão 4 era necessário instalar uma biblioteca adicional, mas agora isto é nativo. 

Implementação

Na imagem seguinte, temos uma implementação desse middleware:


app.use(function(err: Error, req: Request, res: Response, next: NextFunction) {
    if (err instanceof HTTPException) {
        const error = err as HTTPException
        res.status(error.statusCode).send({ error: err.message })
        return;
    }
    else {
        res.status(500).send({ error: err.message })
        return;
    }
    next();
})


Para isto criamos uma exception chamada: HTTPException.


export class HTTPException extends Error {
    public readonly statusCode: number
    constructor(message: string, statusCode: number) {
        super(message);
        this.name = "HTTPException";
        this.statusCode = statusCode
    }
}


Depois nas rotas, basta fazer throw da exception:


async function createTask() {
    await wait();
    throw new HTTPException("Error creating task", 500);
}
app.post("/tasks", authMiddleware, async(req: Request, res: Response) => {
    const mappedRequest = fromExpressRequestToRegisterTaskDto(req);
    await createTask();
    res.status(201).json(mappedRequest)
})


Nesta função é executada a função createTask() que devolve uma exception com erro 500.


O erro 500 é devolvido na resposta HTTP com a respetiva descrição do erro.


Se eu remover a “description” do body o erro 400 é devolvido:


Conclusão

Com este método temos apenas um único local onde os erros são tratados. E desta forma, evitamos o uso de muitos try/catch.