# Rozmowa NestJS: Guards, Interceptory i architektura modułowa > Częste pytania na rozmowach technicznych z NestJS dotyczące Guards, Interceptorów i architektury modułowej, z konkretnymi przykładami kodu w TypeScript i wyjaśnieniami technicznymi. - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- Rozmowy techniczne z NestJS konsekwentnie skupiają się na trzech filarach frameworka: Guards, Interceptorach i architekturze modułowej. Mechanizmy te tworzą szkielet każdej dobrze zaprojektowanej aplikacji NestJS, a rekruterzy używają ich do oceny rzeczywistego opanowania frameworka. > **Co oceniają rekruterzy** > > Guard kontroluje dostęp (kto wchodzi), Interceptor transformuje dane (co wchodzi i wychodzi), a architektura modułowa wszystko organizuje. Opanowanie wszystkich trzech demonstruje głębokie zrozumienie NestJS wykraczające poza podstawowe operacje CRUD. ## Jak działają Guards w NestJS i typowe scenariusze rozmów Guards implementują interfejs `CanActivate` i decydują, czy żądanie dotrze do handlera. W przeciwieństwie do middleware, Guards mają dostęp do kontekstu wykonania NestJS (`ExecutionContext`), co umożliwia podejmowanie decyzji autoryzacyjnych na podstawie metadanych handlera. Klasyczne pytanie: "Zaimplementuj Guard, który sprawdza role użytkownika." ```typescript // roles.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { // Retrieve roles defined via the @Roles() decorator const requiredRoles = this.reflector.getAllAndOverride('roles', [ context.getHandler(), context.getClass(), ]); // No roles required means the route is public if (!requiredRoles) return true; // Extract user from the HTTP request const { user } = context.switchToHttp().getRequest(); // Check if the user holds at least one required role return requiredRoles.some((role) => user.roles?.includes(role)); } } ``` `Reflector` odczytuje metadane dołączone przez własne dekoratory. Odpowiadający dekorator `@Roles()` wygląda tak: ```typescript // roles.decorator.ts import { SetMetadata } from '@nestjs/common'; // Creates a decorator that attaches roles as metadata export const Roles = (...roles: string[]) => SetMetadata('roles', roles); ``` Mocna odpowiedź na rozmowie wykracza poza sam kod. Kolejność wykonania ma znaczenie: Middleware → Guards → Interceptory (przed) → Pipes → Handler → Interceptory (po) → Exception Filters. Ta sekwencja pojawia się praktycznie w każdej rozmowie z NestJS. ## Interceptory NestJS: transformacja żądań i odpowiedzi Interceptory implementują `NestInterceptor` i wykorzystują RxJS do manipulowania strumieniem danych przed i po handlerze. Ich siła wynika z dostępu do `CallHandler`, który reprezentuje pipeline wykonania. Częste pytanie: "Stwórz Interceptor logujący, który mierzy czas wykonania." ```typescript // logging.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(LoggingInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const { method, url } = request; const now = Date.now(); // next.handle() invokes the route handler return next.handle().pipe( tap(() => { // Runs AFTER the handler responds const duration = Date.now() - now; this.logger.log(`${method} ${url} — ${duration}ms`); }), ); } } ``` Operator RxJS `tap` obserwuje strumień bez jego modyfikowania. Aby przekształcić odpowiedź, właściwym operatorem jest `map`: ```typescript // transform.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; // Generic interface for a standardized API response interface ApiResponse { data: T; timestamp: string; path: string; } @Injectable() export class TransformInterceptor implements NestInterceptor> { intercept(context: ExecutionContext, next: CallHandler): Observable> { const request = context.switchToHttp().getRequest(); return next.handle().pipe( map((data) => ({ data, timestamp: new Date().toISOString(), path: request.url, })), ); } } ``` Ten wzorzec ustandaryzowanej odpowiedzi punktuje na rozmowach: pokazuje umiejętność myślenia o projekcie API i spójności odpowiedzi. > **Guard vs Interceptor vs Middleware** > > Middleware: surowe przetwarzanie HTTP (jak w Express). Guard: logiczna decyzja o dostępie. Interceptor: transformacja strumienia żądania/odpowiedzi za pomocą RxJS. Pomylenie tych trzech pojęć na rozmowie to natychmiastowy sygnał ostrzegawczy. ## Modułowa architektura NestJS: budowanie skalowalnych aplikacji Architektura modułowa oddziela na rozmowach juniorów od seniorów. NestJS wymusza organizację opartą na modułach, ale prawdziwe pytania dotyczą decyzji architektonicznych: kiedy tworzyć moduł i jak zarządzać zależnościami między modułami. Zaawansowane pytanie: "Jak zaprojektowałbyś aplikację e-commerce w NestJS z poprawnie odseparowanymi modułami?" ```typescript // orders/orders.module.ts import { Module } from '@nestjs/common'; import { OrdersService } from './orders.service'; import { OrdersController } from './orders.controller'; import { PaymentsModule } from '../payments/payments.module'; import { ProductsModule } from '../products/products.module'; @Module({ // Import modules that OrdersModule depends on imports: [PaymentsModule, ProductsModule], controllers: [OrdersController], providers: [OrdersService], // Expose OrdersService to modules that import OrdersModule exports: [OrdersService], }) export class OrdersModule {} ``` Klasyczna pułapka rozmowy: zależności cykliczne. Jeśli `OrdersModule` importuje `PaymentsModule`, a `PaymentsModule` importuje `OrdersModule`, NestJS zawodzi przy starcie. Wyjście awaryjne korzysta z `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Najmocniejsza odpowiedź wyjaśnia, że `forwardRef` to obejście, a nie rozwiązanie. Właściwym podejściem jest refaktoryzacja polegająca na wyodrębnieniu wspólnego modułu (`SharedOrderPaymentModule`) lub użycie Event Emittera w celu odseparowania komunikacji. ## Dynamic Modules i zaawansowana konfiguracja Dynamic Modules tworzą konfigurowalne moduły wielokrotnego użytku w wielu projektach. Ten wzorzec pojawia się na rozmowach na poziomie senior. Pytanie: "Stwórz konfigurowalny moduł cache z dynamicznymi opcjami." ```typescript // cache/cache.module.ts import { Module, DynamicModule, Global } from '@nestjs/common'; import { CacheService } from './cache.service'; // Module configuration interface export interface CacheModuleOptions { ttl: number; // Time-to-live in seconds maxItems: number; // Maximum number of entries prefix: string; // Key prefix } @Global() // Available everywhere without explicit import @Module({}) export class CacheModule { static forRoot(options: CacheModuleOptions): DynamicModule { return { module: CacheModule, providers: [ { // Inject options via a custom token provide: 'CACHE_OPTIONS', useValue: options, }, CacheService, ], exports: [CacheService], }; } } ``` Odpowiadający serwis wstrzykuje opcje przez `@Inject()`: ```typescript // cache/cache.service.ts import { Injectable, Inject } from '@nestjs/common'; import { CacheModuleOptions } from './cache.module'; @Injectable() export class CacheService { private store = new Map(); constructor(@Inject('CACHE_OPTIONS') private options: CacheModuleOptions) {} set(key: string, value: any): void { const prefixedKey = `${this.options.prefix}:${key}`; this.store.set(prefixedKey, { value, expiry: Date.now() + this.options.ttl * 1000, }); } get(key: string): T | null { const prefixedKey = `${this.options.prefix}:${key}`; const entry = this.store.get(prefixedKey); // Check expiration before returning if (!entry || entry.expiry < Date.now()) return null; return entry.value as T; } } ``` Wzorzec `forRoot` / `forRootAsync` to klasyk NestJS. `forRootAsync` wczytuje konfigurację z wstrzykiwalnego `ConfigService`, co stanowi rekomendowaną praktykę produkcyjną. > **Pułapka rozmowy: @Global()** > > Oznaczanie modułu jako `@Global()` wydaje się wygodne, ale nadużywanie tego tworzy niewidzialne sprzężenia. Wspomnienie podczas rozmowy, że `@Global()` należy zarezerwować dla usług przekrojowych (config, cache, logger), świadczy o dojrzałości architektonicznej. ## Własne dekoratory i kompozycja Guards Własne dekoratory łączą wiele Guards i metadane w jednej adnotacji. Ten temat pojawia się na stanowiskach mid-senior. ```typescript // auth.decorator.ts import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common'; import { JwtAuthGuard } from './jwt-auth.guard'; import { RolesGuard } from './roles.guard'; // Combines JWT authentication + role verification export function Auth(...roles: string[]) { return applyDecorators( SetMetadata('roles', roles), UseGuards(JwtAuthGuard, RolesGuard), ); } ``` Użycie w kontrolerze: ```typescript // orders/orders.controller.ts import { Controller, Get, Post, Body } from '@nestjs/common'; import { Auth } from '../auth/auth.decorator'; import { OrdersService } from './orders.service'; @Controller('orders') export class OrdersController { constructor(private ordersService: OrdersService) {} @Get() @Auth('admin', 'manager') // Single decorator replaces UseGuards + Roles findAll() { return this.ordersService.findAll(); } @Post() @Auth('admin') create(@Body() dto: CreateOrderDto) { return this.ordersService.create(dto); } } ``` Wzorzec `applyDecorators` upraszcza kod kontrolera i centralizuje logikę autoryzacji. Proaktywne zaproponowanie tego podejścia podczas rozmowy to silny sygnał. ## Podsumowanie - Guards używają `CanActivate` i `Reflectora` do decyzji o dostępie na podstawie metadanych handlera - Interceptory wykorzystują RxJS (`tap`, `map`) do transformacji strumienia przed i po handlerze - Kolejność wykonania Middleware → Guards → Interceptory → Pipes → Handler to niemal uniwersalne pytanie - Cykliczne zależności należy rozwiązywać przez refaktoryzację architektoniczną, nie przez `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) demonstrują umiejętność budowania komponentów wielokrotnego użytku - `applyDecorators` łączy wiele Guards w jeden czytelny dekorator - Ćwicz te pytania na realnym kodzie w [modułach NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) i [Interceptorach](/technologies/node-nestjs/interview-questions/middleware-interceptors) --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture