Cum să utilizați filtrele de excepție Nest.js pentru a gestiona erorile

În Nest.js, filtrele de excepții reprezintă o modalitate eficientă de a intercepta și de a gestiona erorile, fie la nivelul întregii aplicații, fie specific pentru fiecare controler.

Aceste filtre permit centralizarea logicii de tratare a erorilor, personalizarea formatului răspunsurilor la erori și asigurarea unei abordări consistente în gestionarea problemelor apărute în aplicație. În acest articol, vom explora ce sunt filtrele de excepții și cum pot fi utilizate pentru a gestiona eficient erorile în cadrul aplicațiilor.

Gestionarea implicită a erorilor în Nest.js

Nest.js include un strat implicit de gestionare a excepțiilor, care preia controlul atunci când codul aplicației nu tratează explicit o eroare.

Atunci când în aplicație apare o eroare care nu este gestionată, Nest.js o interceptează și transmite clientului un răspuns cu codul 500, indicând o eroare internă de server. În acest caz, răspunsul JSON generat de Nest.js are următoarea structură:

 {
  "statusCode": 500,
  "message": "Internal server error"
}

În cazul în care obiectul de eroare aruncat de cod conține deja un `statusCode` și un `message`, Nest.js va folosi aceste valori în loc de răspunsul implicit.

Pentru a evita acest comportament generic și a oferi un răspuns mai informativ clientului, este necesar să gestionați cu atenție toate erorile care pot apărea în aplicație. Acest lucru se poate realiza prin utilizarea filtrelor de excepții, fie cele predefinite, fie unele personalizate, oferite de Nest.js.

Crearea unui filtru de excepții personalizat

Pentru a exemplifica procesul de creare a unui filtru de excepții personalizat, vom crea unul care să gestioneze toate excepțiile HTTP.

Începem prin a crea un fișier numit `http.exception.ts` și adăugăm următoarele importuri:

 import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';

Aceste importuri au următoarele roluri:

  • `ExceptionFilter`: Este o interfață care definește structura unui filtru de excepții.
  • `Catch`: Este un decorator care marchează o clasă ca fiind un filtru de excepții în Nest.js.
  • `ArgumentsHost`: Această interfață oferă metode de a accesa argumentele transmise unui handler. Permite selectarea contextului de execuție adecvat (de exemplu, HTTP, RPC sau WebSockets) pentru a accesa argumentele.
  • `HttpException`: Este clasa de bază care definește o excepție HTTP în Nest.
  • `Request` și `Response`: Sunt interfețele care descriu obiectele de cerere și răspuns din Express.js.

Apoi, creăm o clasă, `HttpExceptionFilter`, care implementează `ExceptionFilter`. O adnotăm cu decoratorul `Catch` pentru a specifica că aceasta va gestiona `HttpException`-urile:

 @Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {}

În continuare, completăm clasa cu următorul cod:

 catch(exception: HttpException, host: ArgumentsHost) {
    
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    
    const request = ctx.getRequest<Request>();
    
    const status = exception.getStatus();
    
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message:
        exception.message
        || exception.getResponse()['message']
        || 'Internal Server Error',
    });
}

Acest cod preia obiectele de cerere și răspuns din `ArgumentsHost` și extrage informații relevante din excepție. Apoi, transmite clientului un răspuns JSON structurat, conținând detalii despre eroare.

Asocierea filtrelor de excepții

Un filtru de excepții poate fi asociat fie unui controler specific, fie întregii aplicații, în funcție de cerințe.

Pentru a asocia un filtru de excepții la nivel global, trebuie importat filtrul în fișierul `main.ts`. Apoi, o instanță a filtrului de excepții este transmisă metodei `app.useGlobalFilters`:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './exception/http.exception';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  
  app.useGlobalFilters(new HttpExceptionFilter());

  await app.listen(4050);
}
bootstrap();

Pentru a asocia un filtru unei clase de controler, se importă decoratorul `UseFilters` și filtrul de excepții. Clasa de controler este adnotată cu `@UseFilters`, transmițând o instanță a filtrului ca argument:

 @Controller()
@UseFilters(new HttpExceptionFilter())
export class AppController {}

Locul unde este asociat filtrul determină și sfera de acoperire a tratării erorilor. Filtrele asociate la nivel de controler vor deservi doar controlerul respectiv, în timp ce filtrele asociate la nivel de aplicație vor gestiona erorile la nivel global.

Utilizarea excepțiilor predefinite pentru a genera erori

Nest.js oferă clase de excepții predefinite care pot fi utilizate pentru a genera erori în mod standardizat.

De exemplu, pentru a genera o eroare cu codul de stare 404, se poate utiliza clasa `NotFoundException`:

  getUserById(id: number) {
    const user = users.find((user) => user.id === id);

    if (!user) {
      throw new NotFoundException({
        message: `User with id ${id} not found`,
      });
    }
  }

Acest cod verifică dacă utilizatorul cu ID-ul specificat există. În caz contrar, se generează o eroare 404 folosind `NotFoundException`, transmițând un mesaj ca argument.

Clase uzuale de excepții predefinite

Alte clase de excepții predefinite includ, dar nu se limitează la:

  • `BadRequestException`: Generează o excepție care indică o cerere invalidă, cu codul de stare 400. Se utilizează atunci când cererea clientului este incorectă sau nevalidă, iar serverul nu o poate procesa.
  • `UnauthorizedException`: Generează o excepție care indică accesul neautorizat, cu codul de stare 401. Se utilizează când un utilizator nu este autentificat sau nu are permisiunile necesare pentru a accesa o resursă.
  • `ForbiddenException`: Generează o excepție care indică accesul interzis, cu codul de stare 403. Se utilizează atunci când un utilizator este autentificat, dar nu este autorizat să efectueze o anumită acțiune.
  • `RequestTimeoutException`: Generează o excepție care indică expirarea cererii, cu codul de stare 408. Se utilizează când un server încheie o cerere deoarece procesarea a durat prea mult.
  • `ConflictException`: Generează o excepție care indică un conflict, cu codul de stare 409. Se utilizează atunci când există un conflict între cererea clientului și starea curentă a resursei.
  • `InternalServerErrorException`: Generează o excepție care indică o eroare internă a serverului, cu codul de stare 500. Se utilizează atunci când apare o eroare neașteptată pe server.

Cele mai bune practici pentru gestionarea erorilor în Nest.js

Atunci când gestionați erori în Nest.js, asigurați-vă că folosiți filtre de excepții pentru a intercepta și a gestiona erorile, fie global, fie per controler. Puteți crea și filtre personalizate pentru anumite tipuri de excepții.

De asemenea, utilizați clasele de excepții predefinite corespunzătoare pentru a genera erori standardizate și relevante. Aplicarea acestor practici va îmbunătăți semnificativ fiabilitatea aplicațiilor dumneavoastră Nest.js.