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.

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.
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."
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<string[]>('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:
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."
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<any> {
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:
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<T> {
data: T;
timestamp: string;
path: string;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
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.
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?"
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:
// 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.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
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."
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():
import { Injectable, Inject } from '@nestjs/common';
import { CacheModuleOptions } from './cache.module';
@Injectable()
export class CacheService {
private store = new Map<string, { value: any; expiry: number }>();
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<T>(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ą.
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.
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:
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ą
CanActivateiReflectorado 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 i Interceptorach
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

NestJS + Prisma: nowoczesny stack backendowy dla Node.js
Kompletny przewodnik po budowaniu nowoczesnego API backendowego z NestJS i Prismą. Konfiguracja, modele, serwisy, transakcje i dobre praktyki.

Pytania na rozmowie kwalifikacyjnej z Node.js Backend: Kompletny przewodnik 2026
25 najczęściej zadawanych pytań na rozmowie kwalifikacyjnej z Node.js backend. Event loop, async/await, strumienie, klasteryzacja i wydajność z szczegółowymi odpowiedziami.

NestJS: Budowanie kompletnego REST API
Kompletny przewodnik po budowaniu profesjonalnego REST API z NestJS. Kontrolery, serwisy, moduły, walidacja z class-validator i obsługa błędów z praktycznymi przykładami.