Kurs NestJS · Moduł 2: Routing i cykl żądania

Cykl życia żądania w NestJS - droga depeszy przez imperium

5 min czytania
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ź HTTP

Każ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:

  1. Middleware - pierwszy punkt kontrolny (surowy request)
  2. Guards - kontrola uprawnień (wpuścić/nie wpuścić)
  3. Interceptors before - początek obserwacji
  4. Pipes - walidacja i transformacja danych
  5. Handler - logika biznesowa
  6. Interceptors after - koniec obserwacji, transformacja odpowiedzi
  7. 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");
48

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Który element NestJS wykonuje się jako PIERWSZY w cyklu życia żądania HTTP?

  2. 2. Co wyróżnia Interceptory na tle innych elementów pipeline'u NestJS?

  3. 3. Co się stanie, gdy Guard odrzuci request (zwróci false)? Które elementy pipeline'u się NIE wykonają?

  4. 4. Interceptor loguje czas w tap() po odpowiedzi, a pipe rzuca BadRequestException. Co jeszcze się wykona?

  5. 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:

Przydatne artykuły