Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Exception Filters - sady i trybunaly imperium

Architekcie imperium! Pretor Augustus, najwyzszy sedzia Rzymu, zauważyl, ze kazdy system potrzebuje sprawnego wymiaru sprawiedliwosci. Gdy cos pojdzie nie tak - brakujacy tribut, nieautoryzowany dostep, blad serwera - potrzebujemy sposobu na eleganckie obsluzenie tych sytuacji. Czas nauczyc sie Exception Filters - systemu sadow i trybunałow NestJS!

Czym sa Exception Filters?

W imperium rzymskim kazdy rodzaj przestepstwa mial swoj trybunal:

  • Trybunal pretorski - sprawy cywilne (404 Not Found)
  • Sad cenzorski - kontrola dostępu (403 Forbidden)
  • Trybunal wojskowy - powazne naruszenia (500 Internal Error)

Exception Filters w NestJS dzialaja identycznie - przechwytuja bledy i zamieniaja je na czytelne odpowiedzi HTTP.

Wbudowane wyjatki NestJS

NestJS dostarcza bogata hierarchie wbudowanych wyjatkow HTTP:

1import {
2  BadRequestException,      // 400
3  UnauthorizedException,     // 401
4  ForbiddenException,        // 403
5  NotFoundException,         // 404
6  ConflictException,         // 409
7  InternalServerErrorException, // 500
8  HttpException,             // bazowy wyjatek HTTP
9} from '@nestjs/common';
10
11@Controller('tributa')
12export class TributeController {
13  private tributes = [
14    { id: 1, name: 'Aurum Galliae', amount: 50000 },
15    { id: 2, name: 'Argentum Hispaniae', amount: 30000 },
16  ];
17
18  @Get(':id')
19  findTribute(@Param('id', ParseIntPipe) id: number) {
20    const tribute = this.tributes.find(t => t.id === id);
21    if (!tribute) {
22      // Rzuc wyjatek 404
23      throw new NotFoundException(
24        `Tributum ${id} non inventum! (Tribut nie znaleziony)`
25      );
26    }
27    return tribute;
28  }
29
30  @Post()
31  @UseGuards(RolesGuard)
32  createTribute(@Body() dto: any) {
33    if (!dto.name || !dto.amount) {
34      // Rzuc wyjatek 400
35      throw new BadRequestException(
36        'Tribut musi zawierac nazwe i kwote!'
37      );
38    }
39
40    const exists = this.tributes.find(t => t.name === dto.name);
41    if (exists) {
42      // Rzuc wyjatek 409
43      throw new ConflictException(
44        `Tribut o nazwie "${dto.name}" juz istnieje!`
45      );
46    }
47
48    const newTribute = { id: this.tributes.length + 1, ...dto };
49    this.tributes.push(newTribute);
50    return newTribute;
51  }
52}

Kazdy wbudowany wyjatek automatycznie generuje odpowiedni kod HTTP i strukture odpowiedzi. Mozesz tez uzywac bazowego

HttpException
z dowolnym kodem:

1throw new HttpException('Wiadomosc bledu', HttpStatus.I_AM_A_TEAPOT); // 418

Tworzenie Custom Exception Filters

Wbudowane wyjatki sa swietne, ale czasem potrzebujemy wlasnego formatowania odpowiedzi bledow. Do tego sluzy dekorator

@Catch()
:

1// filters/roman-exception.filter.ts
2import {
3  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
4} from '@nestjs/common';
5import { Request, Response } from 'express';
6
7@Catch(HttpException)
8export class RomanExceptionFilter implements ExceptionFilter {
9  catch(exception: HttpException, host: ArgumentsHost) {
10    const ctx = host.switchToHttp();
11    const response = ctx.getResponse<Response>();
12    const request = ctx.getRequest<Request>();
13    const status = exception.getStatus();
14
15    // Wlasny format odpowiedzi bledu w stylu imperium
16    response.status(status).json({
17      statusCode: status,
18      imperium: 'Roma Aeterna',
19      error: exception.message,
20      path: request.url,
21      method: request.method,
22      timestamp: new Date().toISOString(),
23    });
24  }
25}

Bindowanie filtrow - poziomy trybunalu

Exception Filters mozna bindowac na trzech poziomach:

1// 1. Poziom metody - tylko ta metoda
2@Controller('tributa')
3export class TributeController {
4  @Get(':id')
5  @UseFilters(new RomanExceptionFilter()) // Tylko dla tej metody
6  findTribute(@Param('id') id: string) {
7    throw new NotFoundException('Tribut nie znaleziony!');
8  }
9}
10
11// 2. Poziom kontrolera - caly kontroler
12@Controller('legiones')
13@UseFilters(RomanExceptionFilter) // Caly kontroler
14export class LegionController {
15  @Get(':id')
16  findLegion(@Param('id') id: string) {
17    throw new NotFoundException('Legion nie znaleziony!');
18  }
19}
20
21// 3. Poziom globalny - cala aplikacja (w main.ts)
22// app.useGlobalFilters(new RomanExceptionFilter());

Catch-All Filter - najwyzszy trybunal

Mozesz stworzyc filtr przechwytujacy WSZYSTKIE wyjatki (nie tylko HttpException):

1// filters/all-exceptions.filter.ts
2import {
3  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
4} from '@nestjs/common';
5
6@Catch() // Bez argumentu = lapie WSZYSTKO
7export class AllExceptionsFilter implements ExceptionFilter {
8  catch(exception: unknown, host: ArgumentsHost) {
9    const ctx = host.switchToHttp();
10    const response = ctx.getResponse();
11    const request = ctx.getRequest();
12
13    const status = exception instanceof HttpException
14      ? exception.getStatus()
15      : HttpStatus.INTERNAL_SERVER_ERROR;
16
17    const message = exception instanceof HttpException
18      ? exception.message
19      : 'Blad wewnetrzny serwera imperium!';
20
21    response.status(status).json({
22      statusCode: status,
23      error: message,
24      path: request.url,
25      timestamp: new Date().toISOString(),
26    });
27  }
28}

Kolejnosc wykonania filtrow

Wazne jest zrozumienie kolejnosci - filtry sa wykonywane od najblizszego do najdalszego:

  1. Filtr metody (jesli istnieje) - sprawdzany jako pierwszy
  2. Filtr kontrolera (jesli istnieje) - sprawdzany jako drugi
  3. Filtr globalny (jesli istnieje) - sprawdzany jako ostatni

Jesli filtr metody obsluzy wyjatek, filtr kontrolera i globalny NIE zostana wywolane.

Exception Filters to trybunal Twojego imperium - zapewniaja, ze kazdy blad jest obsluzony w sposob elegancki i zrozumialy dla klientow API. Zaden blad nie przejdzie niezauwazony!

Przejdź do CodeWorlds