Texto de: Arthur Brito
Introdução
Quando estamos falando de desenvolvimento web, a comunicação entre o backend e o frontend é extremamente importante, tanto para a equipe de desenvolvimento conseguir manter a aplicação de uma forma mais eficiente, quanto para os usuários terem uma experiência mais agradável. Entretanto, é comum surgirem erros em uma das camadas da nossa aplicação, e precisamos lidar com eles da melhor forma possível.
Por que tratar erros de forma consistente é importante para as nossas aplicações?
Ter um tratamento de erros padronizado ajuda a centralizar a lógica de tratamento, evita duplicação de código e garante que, mesmo se algo der errado, o usuário veja uma mensagem clara e sem detalhes técnicos desnecessários. Dessa forma, tanto o backend quanto o frontend se comunicam melhor, tornando a manutenção e a evolução do sistema mais fáceis.
Os erros mal tratados em uma aplicação podem gerar bastante dor de cabeça. Por exemplo, mensagens de erros com dados sensíveis ou que não deveriam ser expostos podem acabar retornando ao frontend e deixar sua aplicação vulnerável.

Além disso, é bem mais fácil de identificar problemas de uma maneira prática e rápida quando temos uma mensagem de erro bem formatada e tratada em nossas mãos.
Exemplo básico de rota com NestJS
No backend, a ideia é capturar os erros de forma centralizada e mandar respostas com um formato conhecido. No exemplo abaixo, usamos NestJS para demonstrar como fazer isso, mas o conceito pode ser adaptado para outras tecnologias.
Aqui, usaremos como exemplo bem básico esse snippet de código abaixo.

É um controller bem básico, apenas para tomarmos como exemplo.
Exemplo básico de consumo da API com Next.js
No frontend, é legal criar uma página de erro personalizada para exibir mensagens amigáveis quando algo der errado. O exemplo a seguir mostra como criar uma página de erro simples com Next.js, mas a ideia pode ser adaptada para outras frameworks.

Novamente, usaremos um exemplo bem básico de página no Next, onde estamos apenas dando fetch na mensagem retornada na rota /hello do nosso backend e mostrando esse valor como conteúdo da página.
Só para ter uma noção básica de como funcionaria o fluxo da aplicação, podemos tomar como exemplo a imagem abaixo. O frontend fará a requisição (através do fetch) na rota /hello do nosso backend. Vale lembrar que é importante ter configurado as informações do nosso servidor corretamente no frontend para não haver problemas na requisição.
Ao receber a requisição, o backend vai processá-la e, então, retornar os dados correspondentes. Caso tudo ocorra bem, a mensagem ‘Hello World from NestJS’ deve ser retornada. Caso contrário, um erro será retornado para o frontend.
E, por fim, ao receber as informações vindas do backend, a página Home exibirá a mensagem recebida (sendo um erro ou o dado esperado).

Simulação de erro
Agora que já temos uma comunicação estabelecida, vamos simular um erro no backend para que o frontend capture essa condição e exiba uma mensagem adequada ao usuário.
No backend, podemos criar novamente uma rota exemplo e forçar o lançamento de um erro para termos uma ideia melhor de como tratá-lo.

Agora, no frontend, temos que capturar esse erro e processá-lo de um modo amigável para o usuário final. Confira abaixo:

Dessa maneira, quando damos fetch na rota /error (que garantidamente vai nos retornar um erro), setaremos o estado de error para ser a mensagem de erro retornada do backend. Então, ao tratar isso na interface, mostraremos ao usuário uma mensagem mais personalizada de erro. No exemplo, apenas adicionamos um começo "Erro: " ao erro, mas poderíamos tratar de um modo mais amigável ao usuário final, o que veremos mais na frente.
Assim, ao invés de um erro de várias linhas que polui a tela do usuário, teremos um erro tratado e user-friendly.

Consistência no tratamento de erros
Para garantir uma consistência e padronização maiores no tratamento de erros no backend, podemos implementar um filtro de exceções no NestJS. Assim, qualquer exceção lançada no backend terá sua resposta formatada de maneira consistente.

Desse modo, podemos aplicar esse filtro globalmente e, quando ocorrer um erro no nosso backend, teremos um erro com campos padronizados, o que facilita bastante no tratamento de modo geral.
Testes e validação de erros
Testar o tratamento de erros é fundamental para ter certeza de que tudo funciona como esperado. No backend, frameworks de teste como Jest podem simular exceções e verificar se o filtro de erros está funcionando. Veja um exemplo simples de teste:

Com esse código, podemos testar se a resposta de erro é formatada como esperado. Assim, poderemos ter certeza de que as respostas da nossa API retornarão ao frontend de uma maneira consistente e padronizada.
Dica: Melhor experiência para o usuário com Toasts
Você pode tratar os erros vindos do backend de um jeito ainda mais esteticamente agradável para o usuário final. Com bibliotecas como React Toastify, você pode usar um método relacionado a erros da biblioteca que, ao passar uma mensagem específica como parâmetro, mostra um toast de erro/sucesso/aviso, como o exemplo abaixo:

Assim, o usuário tem um feedback instantâneo sobre o erro e pode verificar como corrigir da melhor forma, seja com uma ação diferente que ele possa executar ou contatando a equipe responsável pela aplicação para verificar o que pode ter ocorrido.
Conclusão
Logo, podemos concluir que implementar um tratamento consistente de erros aumenta a robustez da sua aplicação, simplificando a manutenção e elevando a experiência do usuário. Quando adotamos uma abordagem padronizada (demonstrada por meio de exemplos com Next.js e NestJS), garantimos uma comunicação clara e eficiente entre o backend e o frontend, minimizando a exposição de detalhes técnicos e assegurando que as mensagens de erro sejam informativas e acessíveis.
Essa prática não apenas mantém o sistema mais organizado e de fácil manutenção, mas também aumenta a segurança e a confiabilidade, contribuindo para um ambiente de desenvolvimento mais ágil e para a satisfação dos usuários finais.