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!
W imperium rzymskim kazdy rodzaj przestepstwa mial swoj trybunal:
Exception Filters w NestJS dzialaja identycznie - przechwytuja bledy i zamieniaja je na czytelne odpowiedzi HTTP.
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); // 418Wbudowane 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}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());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}Wazne jest zrozumienie kolejnosci - filtry sa wykonywane od najblizszego do najdalszego:
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!