Executando verificação de segurança...
22

Tratamento de erros no Node.Js

O manuseio de erros é um dos aspectos mais importantes de qualquer aplicação de nível de produção. Qualquer um pode codificar para os casos de sucesso. Somente verdadeiros profissionais cuidam dos casos de erro.

Hoje vamos aprender exatamente isso. Vamos mergulhar.

Primeiro, temos que entender que nem todos os erros são iguais. Vamos ver quantos tipos de erros podem ocorrer em um aplicativo.

  • Erro gerado pelo usuário
  • Falha de hardware
  • Erro de tempo de execução
  • Erro de banco de dados

Veremos como podemos lidar facilmente com esses diferentes tipos de erros.

Obtenha um aplicativo expresso básico.

import express, { Application, Request, Response } from 'express';
const app: Application = express();
const PORT = 3000;

app.use(express.json());

app.get('/', async (req: Request, res: Response): Promise<Response> => {
  return res.status(200).send({
    message: 'Hello World!',
  });
});

app.post('/post', async (req: Request, res: Response): Promise<Response> => {
  console.log(req.body);
  return res.status(200).send({
    message: 'Hello World from post!',
  });
});

try {
  app.listen(PORT, (): void => {
    console.log(`Connected successfully on port ${PORT}`);
  });
} catch (error: any) {
  console.error(`Error occured: ${error.message}`);
}

Manipular erros de URL não encontrados
Como você detecta se um URL de hit não está ativo em seu aplicativo expresso? Você tem uma URL como mas alguém está acessando Precisamos informar que a URL que eles estão tentando acessar não existe ./users,/user.

Isso é fácil de fazer no ExpressJS. Depois de definir todas as rotas, adicione o código a seguir para capturar todas as rotas não correspondentes e enviar uma resposta de erro adequada.

app.use("*", (req: Request, res: Response) => {
  const err = Error(`Requested path ${req.path} not found`);
  res.status(404).send({
    success: false,
    message: "Requested path ${req.path} not found",
    stack: err.stack,
  });
});

main.ts

Aqui estamos usando como um curinga para pegar todas as rotas que não passaram pelo nosso aplicativo.“*”

Lidar com todos os erros com um middleware especial

Agora temos um middleware especial no Express que lida com todos os nossos erros. Temos que incluí-lo no final de todas as rotas e passar todos os erros do nível superior para que este middleware possa lidar com eles para nós.

A coisa mais importante a fazer é manter esse middleware depois de todas as outras definições de middleware e rota, porque, caso contrário, alguns erros desaparecerão.

Vamos adicioná-lo ao nosso arquivo de índice.

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  const statusCode = 500;
  res.status(statusCode).send({
    success: false,
    message: err.message,
    stack: err.stack,
  });
});

errormiddleware.ts

Dê uma olhada na assinatura do middleware. Ao contrário de outros middleware, Este middleware especial tem um parâmetro extra nomeado, que é do tipo. Isso vem como o primeiro parâmetro.err:Error

E modifique nosso código anterior para transmitir o erro como o seguinte.

app.use("*", (req: Request, res: Response, next: NextFunction) => {
  const err = Error(`Requested path ${req.path} not found`);
  next(err);
});

Agora, se atingirmos uma URL aleatória, algo como, então obteremos uma resposta de erro adequada com a pilha.http://localhost:3001/posta

{
  "success": false,
  "message": "Requested path ${req.path} not found",
  "stack": "Error: Requested path / not found\n  at  
             $USER/nodejserro/src/index.ts:23:15\n"
}

Objeto de erro personalizado
Vejamos atentamente o objeto de erro padrão fornecido pelo Node.js.

interface Error {
  name: string;
  message: string;
  stack?: string;
}

Então, quando você está lançando um erro como o seguinte.

throw new Error("Some message");

Então você está apenas recebendo o nome e as propriedades opcionais com ele. Esta pilha nos fornece informações sobre onde exatamente o erro foi produzido. Não queremos incluí-lo na produção. Veremos como fazer isso mais tarde.stack

Mas podemos querer adicionar mais informações ao próprio objeto de erro.

Além disso, podemos querer diferenciar entre vários objetos de erro.

Vamos criar uma classe de erro Custom básica para nosso aplicativo.

export class ApiError extends Error {  
   statusCode: number;  
   constructor(statusCode: number, message: string) {
      super(message);
      this.statusCode = statusCode;    
      Error.captureStackTrace(this, this.constructor);  
   }
}

apiError.ts

Observe a seguinte linha.

Error.captureStackTrace(this, this.constructor);

Ele ajuda a capturar o rastreamento de pilha do erro de qualquer lugar no aplicativo.

Nesta aula simples, podemos acrescentar o também. Vamos modificar nosso código anterior como o seguinte.statusCode

app.use("*", (req: Request, res: Response, next: NextFunction) => {
  const err = new ApiError(404, `Requested path ${req.path} not found`);
  next(err);
});

err.ts

E aproveite a newproperty no middleware do manipulador de erros tambémstatusCode

app.use((err: ApiError, req: Request, res: Response, next: NextFunction) => {
  const statusCode = err.statusCode || 500; // <- Veja aqui
  res.status(statusCode).send({
    success: false,
    message: err.message,
    stack: err.stack,
  });
});

err.ts

Uma classe Error personalizada torna sua API previsível para os usuários finais. A maioria dos novatos sente falta dessa parte.

Vamos lidar com erros de aplicativo.
Agora vamos lançar um erro personalizado de dentro de nossas rotas também.

app.get("/protected", async (req: Request, res: Response, next: NextFunction) => {
  try {
    throw new ApiError(401, "You are not authorized to access this!"); // <- erro falso
  } catch (err) {
    next(err);
  }
});

manualError.ts

Esta é uma situação criada artificialmente em que precisamos lançar um erro. Na vida real, podemos ter muitas situações em que precisamos usar esse tipo de bloco try/catch para pegar erros.

Se atingirmos o seguinte URL, obteremos a seguinte resposta.http://localhost:3001/protected

{
  "success": false,
  "message": "You are not authorized to access this!",
  "stack": "Some details"
}

Portanto, nossa resposta de erro está funcionando corretamente!

Vamos melhorar isso!

Assim, agora podemos lidar com nossos erros personalizados de qualquer lugar no aplicativo. Mas requer um bloco try-catch em todos os lugares e requer chamar a função com o objeto de erro.next

Isso não é o ideal. Isso fará com que nosso código pareça ruim em pouco tempo.

Vamos criar uma função wrapper personalizada que irá capturar todos os erros e chamar a próxima função de um local central.

Vamos criar um utilitário wrapper para este fim!

import { Request, Response, NextFunction } from "express";

export const asyncWrapper = (fn: any) => (req: Request, res: Response, next: NextFunction) => {
  
  Promise.resolve(fn(req, res, next)).catch((err) => next(err));
  
};

asyncWrapper.ts

E use-o dentro do nosso roteador.

import { asyncWrapper } from "./utils/asyncWrapper";

app.get(
  
  "/protected",
  
  asyncWrapper(async (req: Request, res: Response) => {
    throw new ApiError(401, "You are not authorized to access this!");
  })
  
);

wrapper.ts

Execute o código e veja se temos os mesmos resultados. Isso nos ajuda a eliminar todos os blocos try/catch e chamar a próxima função em todos os lugares!

Exemplo de um erro personalizado
Podemos ajustar nossos erros às nossas necessidades. Vamos criar uma nova classe de erro para as rotas não encontradas.

export class NotFoundError extends ApiError {
  constructor(path: string) {
    super(404, `The requested path ${path} not found!`);
  }
}

notFoundError.ts

E simplifique nosso mau manipulador de rotas.

app.use((req: Request, res: Response, next: NextFunction) => next(new NotFoundError(req.path)));

Quão limpo é isso?

Agora vamos instalar um pequeno pacote para evitar escrever os códigos de status nós mesmos.

yarn add http-status-codes

E adicione o código de status de uma maneira significativa.

export class NotFoundError extends ApiError {
  constructor(path: string) {
    super(StatusCodes.NOT_FOUND, `The requested path ${path} not found!`);
  }
}

notFoundError.ts

E dentro da nossa rota assim.

app.get(
  "/protected",
  asyncWrapper(async (req: Request, res: Response) => {
    throw new ApiError(StatusCodes.UNAUTHORIZED, "You are not authorized to access this!");
  })
);

improved.ts

Isso apenas torna nosso código um pouco melhor.

Lidar com erros do programador.
A melhor maneira de lidar com erros do programador é reiniciar normalmente. Coloque a seguinte linha de código no final do seu aplicativo. Ele será invocado se algo não for capturado no middleware de erro.

process.on("uncaughtException", (err: Error) => {
  console.log(err.name, err.message);
  console.log("UNCAUGHT EXCEPTION! 💥 Shutting down...");
  process.exit(1);
});

uncaughtException.ts

Lide com rejeições de promessas não tratadas.
Podemos registrar o motivo da rejeição da promessa. Esses erros nunca chegam ao nosso manipulador de erros expresso. Por exemplo, se quisermos acessar um banco de dados com a senha errada.

process.on("unhandledRejection", (reason: Error, promise: Promise<any>) => {
  console.log(reason.name, reason.message);
  console.log("UNHANDLED REJECTION! 💥 Shutting down...");
  process.exit(1);
  throw reason;
});

unhandledRejection.ts

Melhorias adicionais
Vamos criar uma nova classe ErrorHandler para manipular os erros em um local central.

import { Request, Response, NextFunction } from "express";
import { ApiError } from "./apiError";

export default class ErrorHandler {
  static handle = () => {
    return async (err: ApiError, req: Request, res: Response, next: NextFunction) => {
      const statusCode = err.statusCode || 500;
      res.status(statusCode).send({
        success: false,
        message: err.message,
        rawErrors: err.rawErrors ?? [],
        stack: err.stack,
      });
    };
  };
}

errorHandler.ts

Este é apenas um middleware simples do manipulador de erros. Você pode adicionar sua lógica personalizada aqui. E use-o dentro do nosso arquivo de índice.

app.use(ErrorHandler.handle());

É assim que podemos separar as preocupações, respeitando o princípio de responsabilidade única do SOLID.

Espero que você tenha aprendido algo novo hoje. Tenha um descanso maravilhoso do seu dia!

img

Carregando publicação patrocinada...
2

Que publicação sensacioal BrunaoSolitario! Isso me lembrou uma época que eu fiquei viciado sobre o assunto de tratar erros em Node.js e JavaScript, tanto que fiz uma palestra no canal do Pagar.me Talks:

Error handling com JavaScript e Promises
https://youtu.be/ZgWyha2d6iY

Essa apresentação complementa muito bem essa sua publicação 🤝

1
1
1
1

Gostei bastante da publicação. Eu estou usando express-async-errors no momento. Este abordagem parece fazer exatamente o que a biblioteca faz. Tu acha que eu poderia trocar a biblioteca por esse código e teria o mesmo resultado?

1

Obrigado pelo seu comentário! A abordagem parece fazer o mesmo que a biblioteca express-async-errors, mas é possível que haja algumas diferenças menores entre os dois. Eu sugiro que você teste a abordagem descrita na publicação e veja se ela funciona para o seu caso específico. A biblioteca express-async-errors pode ter recursos adicionais que podem ser úteis para o seu aplicativo, então talvez valha a pena continuar usando-a. No final, a decisão de qual abordagem usar depende das necessidades do seu aplicativo e do que funciona melhor para você.

1