Kurs NestJS · Moduł 2: Routing i cykl żądania
Cykl życia żądania w NestJS - droga depeszy przez imperium
W tej lekcji5
Architekcie imperium! Architekt Vitruvius, mistrz budowli i systemów, przygotował dla Ciebie najważniejszą lekcję tego modułu. Poznałeś już middleware, guardy, interceptory, pipes, własne dekoratory i filtry wyjątków. Teraz czas zobaczyć, jak to WSZYSTKO łączy się w jedną całość - pełny cykl życia żądania HTTP w NestJS!
Pełna droga depeszy przez imperium
Wyobraź sobie, że depesza (request HTTP) wyrusza z dalekiej prowincji do Cezara. Po drodze przechodzi przez wiele punktów kontrolnych. Oto dokładna kolejność:
1Przychodzące żądanie HTTP
2 |
3 v
4 1. MIDDLEWARE (Strażnik bramy)
5 |
6 v
7 2. GUARDS (Kontrola uprawnień)
8 |
9 v
10 3. INTERCEPTORS - before (Kronikarz - początek)
11 |
12 v
13 4. PIPES (Kontrola jakości danych)
14 |
15 v
16 5. ROUTE HANDLER (Cezar podejmuje decyzję)
17 |
18 v
19 6. INTERCEPTORS - after (Kronikarz - koniec)
20 |
21 v
22 7. EXCEPTION FILTERS (Trybunał - w razie błędu)
23 |
24 v
25 Odpowiedź HTTPKażdy etap w szczegółach
1. Middleware - strażnik bramy
Middleware wykonuje się PIERWSZY, zanim NestJS w ogóle wie, która metoda kontrolera zostanie wywołana. Ma dostęp do surowego obiektu Request i Response:
1// Middleware wykonuje się jako PIERWSZY
2@Injectable()
3export class LoggerMiddleware implements NestMiddleware {
4 use(req: Request, res: Response, next: NextFunction) {
5 console.log('[1. MIDDLEWARE] Depesza dotarła do bramy');
6 console.log('Metoda: ' + req.method + ', Ścieżka: ' + req.originalUrl);
7 next(); // Przekaż dalej
8 }
9}2. Guards - kontrola uprawnień
Guards wykonują się PO middleware, ale PRZED interceptorami i pipe'ami. Ich jedynym zadaniem jest odpowiedzieć na pytanie: "Czy ten request ma prawo kontynuować?":
1// Guard wykonuje się jako DRUGI
2@Injectable()
3export class AuthGuard implements CanActivate {
4 canActivate(context: ExecutionContext): boolean {
5 console.log('[2. GUARD] Sprawdzam uprawnienia legionisty');
6 const request = context.switchToHttp().getRequest();
7 return !!request.headers.authorization; // true = wpuść, false = zablokuj
8 }
9}3. Interceptors (before) - kronikarz początek
Interceptory mają unikalną właściwość - wykonują się dwukrotnie: raz PRZED handlerem i raz PO nim. Faza "before" to kod przed next.handle():
1// Interceptor - faza BEFORE (trzeci krok)
2@Injectable()
3export class TimingInterceptor implements NestInterceptor {
4 intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
5 const start = Date.now();
6 console.log('[3. INTERCEPTOR before] Kronikarz rozpoczyna zapis');
7
8 return next.handle().pipe(
9 // Ta część to faza AFTER (szósty krok)
10 tap(() => {
11 const duration = Date.now() - start;
12 console.log('[6. INTERCEPTOR after] Czas: ' + duration + 'ms');
13 }),
14 );
15 }
16}4. Pipes - kontrola jakości danych
Pipes wykonują się BEZPOŚREDNIO przed handlerem. Transformują i walidują dane wejściowe:
1// Pipe wykonuje się jako CZWARTY
2@Injectable()
3export class ParseLegionIdPipe implements PipeTransform {
4 transform(value: any) {
5 console.log('[4. PIPE] Waliduję dane wejściowe: ' + value);
6 const id = parseInt(value, 10);
7 if (isNaN(id)) {
8 throw new BadRequestException('ID musi być liczbą!');
9 }
10 return id;
11 }
12}5. Route Handler - decyzja Cezara
Handler to docelowa metoda kontrolera - tutaj wykonuje się logika biznesowa:
1@Controller('legiones')
2export class LegionController {
3 @Get(':id')
4 @UseGuards(AuthGuard)
5 @UseInterceptors(TimingInterceptor)
6 findLegion(@Param('id', ParseLegionIdPipe) id: number) {
7 console.log('[5. HANDLER] Cezar przetwarza zapytanie o legion ' + id);
8 return { id, name: 'Legio X Gemina', commander: 'Marcus' };
9 }
10}6. Interceptors (after) - kronikarz koniec
Po zakończeniu handlera, sterowanie wraca do interceptora - do kodu wewnątrz pipe(tap(...)). Tu możesz transformować odpowiedź, logować czas, itp.
7. Exception Filters - trybunał
Exception Filters wykonują się TYLKO gdy zostanie rzucony wyjątek - na dowolnym etapie. Jeśli guard rzuci ForbiddenException, filtr to przechwyci. Jeśli pipe rzuci BadRequestException - też:
1// Exception Filter - łapie błędy z KAŻDEGO etapu
2@Catch(HttpException)
3export class ImperiumExceptionFilter implements ExceptionFilter {
4 catch(exception: HttpException, host: ArgumentsHost) {
5 console.log('[7. EXCEPTION FILTER] Trybunał obsługuje błąd');
6 const ctx = host.switchToHttp();
7 const response = ctx.getResponse();
8 response.status(exception.getStatus()).json({
9 error: exception.message,
10 timestamp: new Date().toISOString(),
11 });
12 }
13}Praktyczne implikacje kolejności
Zrozumienie kolejności jest kluczowe dla debugowania:
1// Przykład: Co się stanie, gdy guard odrzuci request?
2// 1. Middleware - WYKONA SIĘ (logowanie)
3// 2. Guard - ODRZUCI (rzuci ForbiddenException)
4// 3. Interceptor before - NIE wykona się
5// 4. Pipe - NIE wykona się
6// 5. Handler - NIE wykona się
7// 6. Interceptor after - NIE wykona się
8// 7. Exception Filter - WYKONA SIĘ (obsłuży ForbiddenException)
9
10// Przykład: Co się stanie, gdy pipe odrzuci dane?
11// 1. Middleware - WYKONA SIĘ
12// 2. Guard - WYKONA SIĘ (przepuści)
13// 3. Interceptor before - WYKONA SIĘ
14// 4. Pipe - ODRZUCI (rzuci BadRequestException)
15// 5. Handler - NIE wykona się
16// 6. Interceptor after - tap() i map() NIE wykonają się (brak odpowiedzi), ale błąd przepłynie przez jego strumień (złapie go catchError)
17// 7. Exception Filter - WYKONA SIĘ (obsłuży BadRequestException)Debugowanie pipeline'u
Praktyczna technika debugowania - dodaj logi na każdym etapie:
1// middleware
2console.log('[MW] ' + req.method + ' ' + req.originalUrl);
3
4// guard
5console.log('[GUARD] User: ' + request.user?.name);
6
7// interceptor, faza before
8console.log('[INT-B] Handler: ' + context.getHandler().name);
9
10// pipe
11console.log('[PIPE] Value: ' + value + ' -> ' + transformed);
12
13// handler
14console.log('[HANDLER] Processing...');
15
16// interceptor, faza after
17console.log('[INT-A] Duration: ' + duration + 'ms');
18
19// filtr wyjątków
20console.log('[FILTER] Error: ' + exception.message);Gdy zobaczysz logi, możesz dokładnie śledzić drogę requestu przez cały pipeline i szybko znaleźć, na którym etapie występuje problem.
Podsumowanie cyklu życia
Zapamiętaj tę kolejność jak maszerującą kolumnę legionu:
- Middleware - pierwszy punkt kontrolny (surowy request)
- Guards - kontrola uprawnień (wpuścić/nie wpuścić)
- Interceptors before - początek obserwacji
- Pipes - walidacja i transformacja danych
- Handler - logika biznesowa
- Interceptors after - koniec obserwacji, transformacja odpowiedzi
- Exception Filters - obsługa błędów (gdy coś poszło nie tak)
Znajomość tego cyklu to fundament każdego zaawansowanego programisty NestJS - jak znajomość strategii bitewnej dla centuriona!
Kod do tej lekcji: src/request-lifecycle.ts
1// Cykl życia żądania - Droga Depeszy przez Imperium
2console.log("=== PEŁNY CYKL ŻYCIA ŻĄDANIA w NestJS ===\n");
3
4// Kolejność przetwarzania żądania HTTP:
5const lifecycle = [
6 '1. MIDDLEWARE - Strażnik bramy (surowy request)',
7 '2. GUARDS - Kontrola uprawnień (wpuścić/nie)',
8 '3. INTERCEPTORS - Kronikarz - początek (before)',
9 '4. PIPES - Walidacja i transformacja danych',
10 '5. ROUTE HANDLER - Cezar podejmuje decyzję',
11 '6. INTERCEPTORS - Kronikarz - koniec (after)',
12 '7. EXCEPTION FILTERS - Trybunał (w razie błędu)',
13];
14
15lifecycle.forEach(step => console.log(step));
16
17console.log("\n=== SCENARIUSZE ===\n");
18
19console.log("Scenariusz A: Guard odrzuca request");
20console.log(" 1. Middleware -> WYKONANY");
21console.log(" 2. Guard -> ODRZUCA (ForbiddenException)");
22console.log(" 3-6. -> POMINIĘTE");
23console.log(" 7. Exception Filter -> OBSŁUGUJE błąd\n");
24
25console.log("Scenariusz B: Pipe odrzuca dane");
26console.log(" 1. Middleware -> WYKONANY");
27console.log(" 2. Guard -> PRZEPUSZCZA");
28console.log(" 3. Interceptor -> WYKONANY (before)");
29console.log(" 4. Pipe -> ODRZUCA (BadRequestException)");
30console.log(" 5-6. -> POMINIĘTE");
31console.log(" 7. Exception Filter -> OBSŁUGUJE błąd\n");
32
33console.log("Scenariusz C: Wszystko OK");
34console.log(" 1. Middleware -> WYKONANY");
35console.log(" 2. Guard -> PRZEPUSZCZA");
36console.log(" 3. Interceptor -> WYKONANY (before)");
37console.log(" 4. Pipe -> WALIDUJE dane");
38console.log(" 5. Handler -> PRZETWARZA logikę");
39console.log(" 6. Interceptor -> WYKONANY (after)");
40console.log(" 7. Exception Filter -> niepotrzebny\n");
41
42console.log("=== KLUCZOWE ZASADY ===");
43console.log("- Middleware nie zna docelowej metody kontrolera");
44console.log("- Guard decyduje o dostępie (true/false)");
45console.log("- Interceptor widzi request PRZED i PO handlerze");
46console.log("- Pipe waliduje/transformuje BEZPOŚREDNIO przed handlerem");
47console.log("- Exception Filter łapie błędy z KAŻDEGO etapu");
48Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Który element NestJS wykonuje się jako PIERWSZY w cyklu życia żądania HTTP?
2. Co wyróżnia Interceptory na tle innych elementów pipeline'u NestJS?
3. Co się stanie, gdy Guard odrzuci request (zwróci false)? Które elementy pipeline'u się NIE wykonają?
4. Interceptor loguje czas w tap() po odpowiedzi, a pipe rzuca BadRequestException. Co jeszcze się wykona?
5. Na którym etapie cyklu życia żądania mogą zadziałać filtry wyjątków?
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj etapy cyklu życia żądania HTTP w NestJS od pierwszego do ostatniego:
- Edytor kodu
Napisz LifecycleLoggerMiddleware, który loguje metodę i ścieżkę requestu, a następnie wywołuje next()
- Klikanie w kolejności
Ułóż PEŁNĄ kolejność przetwarzania żądania HTTP w NestJS:
- Edytor kodu
Napisz LifecycleAuthGuard sprawdzający nagłówek authorization - zwróć true, jeśli istnieje, i false, jeśli nie
- Układanie w poziomie
Ułóż elementy pobrania obiektu request w metodzie canActivate guarda:
- Edytor kodu
Napisz LifecycleInterceptor, który przed next.handle() zapisuje Date.now(), a w tap(...) po odpowiedzi loguje, ile milisekund trwała obsługa żądania
- Układanie w pionie
Uporządkuj, co dzieje się z żądaniem, gdy guard je odrzuca - od pierwszego etapu do ostatniego:
- Edytor kodu
Napisz LifecycleValidationPipe, który parsuje wartość na liczbę i rzuca BadRequestException, jeśli to nie liczba
- Klikanie w kolejności
Ułóż logi debugowania w kolejności, w jakiej pojawią się w konsoli przy poprawnym requeście: