# NestJS-sollicitatiegesprek: Guards, Interceptors en modulaire architectuur > Veelgestelde vragen in technische NestJS-sollicitatiegesprekken over Guards, Interceptors en modulaire architectuur, met concrete TypeScript-codevoorbeelden en technische uitleg. - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- Technische NestJS-sollicitatiegesprekken richten zich consequent op drie pijlers van het framework: Guards, Interceptors en modulaire architectuur. Deze mechanismen vormen de ruggengraat van elke goed gestructureerde NestJS-applicatie, en recruiters gebruiken ze om de werkelijke beheersing van het framework te beoordelen. > **Wat interviewers beoordelen** > > Een Guard regelt toegang (wie er binnenkomt), een Interceptor transformeert data (wat erin en eruit gaat), en de modulaire architectuur organiseert alles. Het beheersen van alle drie toont een diep begrip van NestJS dat verder gaat dan basale CRUD-operaties. ## Hoe NestJS Guards werken en typische sollicitatiescenario's Guards implementeren de `CanActivate`-interface en bepalen of een verzoek de handler bereikt. In tegenstelling tot middleware hebben Guards toegang tot de NestJS-uitvoeringscontext (`ExecutionContext`), waardoor autorisatiebeslissingen op basis van handler-metadata mogelijk zijn. Klassieke vraag: "Implementeer een Guard die gebruikersrollen controleert." ```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)); } } ``` De `Reflector` leest de metadata die door custom decorators is gekoppeld. De bijbehorende `@Roles()`-decorator ziet er zo uit: ```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); ``` Een sterk antwoord in een sollicitatiegesprek gaat verder dan de code. De uitvoeringsvolgorde is belangrijk: Middleware → Guards → Interceptors (vooraf) → Pipes → Handler → Interceptors (achteraf) → Exception Filters. Deze volgorde komt in vrijwel elk NestJS-gesprek terug. ## NestJS Interceptors: requests en responses transformeren Interceptors implementeren `NestInterceptor` en gebruiken RxJS om de datastroom voor en na de handler te manipuleren. Hun kracht komt uit de toegang tot `CallHandler`, dat de uitvoeringspijplijn vertegenwoordigt. Veelvoorkomende vraag: "Maak een logging-Interceptor die de uitvoeringstijd meet." ```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`); }), ); } } ``` De RxJS-operator `tap` observeert de stream zonder hem te wijzigen. Om de response te transformeren is `map` de juiste operator: ```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, })), ); } } ``` Dit gestandaardiseerde response-patroon scoort goed in een gesprek: het toont het vermogen om na te denken over API-ontwerp en consistentie van responses. > **Guard vs Interceptor vs Middleware** > > Middleware: ruwe HTTP-verwerking (zoals Express). Guard: booleaanse toegangsbeslissing. Interceptor: transformatie van de request/response-stream met RxJS. Deze drie concepten verwarren in een gesprek is een directe red flag. ## Modulaire NestJS-architectuur: schaalbare applicaties bouwen De modulaire architectuur scheidt junior van senior developers tijdens gesprekken. NestJS dwingt een module-gebaseerde organisatie af, maar de echte vragen richten zich op architectuurkeuzes: wanneer een module aan te maken en hoe afhankelijkheden tussen modules te beheren. Gevorderde vraag: "Hoe zou je een NestJS e-commerce-applicatie structureren met goed ontkoppelde modules?" ```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 {} ``` De klassieke valkuil: circulaire afhankelijkheden. Als `OrdersModule` `PaymentsModule` importeert en `PaymentsModule` `OrdersModule` importeert, faalt NestJS bij het opstarten. De noodoplossing gebruikt `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Het sterkste antwoord legt uit dat `forwardRef` een tijdelijke oplossing is, geen echte fix. De juiste oplossing is refactoren door een gedeelde module (`SharedOrderPaymentModule`) te extraheren of een Event Emitter te gebruiken om communicatie te ontkoppelen. ## Dynamic Modules en geavanceerde configuratie Dynamic Modules creëren configureerbare, projectoverstijgend herbruikbare modules. Dit patroon komt voor in senior-gesprekken. Vraag: "Maak een configureerbare cachemodule met dynamische opties." ```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], }; } } ``` De bijbehorende service injecteert de opties via `@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; } } ``` Het patroon `forRoot` / `forRootAsync` is een NestJS-klassieker. `forRootAsync` laadt de configuratie vanuit een injecteerbare `ConfigService`, wat de aanbevolen praktijk in productie is. > **Valkuil: @Global()** > > Een module markeren als `@Global()` lijkt handig, maar overmatig gebruik creëert onzichtbare koppelingen. In een gesprek vermelden dat `@Global()` gereserveerd moet zijn voor cross-cutting services (config, cache, logger) toont architecturale volwassenheid. ## Custom decorators en compositie van Guards Custom decorators combineren meerdere Guards en metadata in één enkele annotatie. Dit onderwerp komt voor bij mid-senior-posities. ```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), ); } ``` Gebruik in een controller: ```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); } } ``` Het `applyDecorators`-patroon vereenvoudigt de controllercode en centraliseert de autorisatielogica. Deze aanpak proactief voorstellen tijdens een gesprek is een sterk signaal. ## Conclusie - Guards gebruiken `CanActivate` en de `Reflector` voor toegangsbeslissingen op basis van handler-metadata - Interceptors benutten RxJS (`tap`, `map`) om de stream voor en na de handler te transformeren - De uitvoeringsvolgorde Middleware → Guards → Interceptors → Pipes → Handler is een bijna universele vraag - Circulaire afhankelijkheden moeten via architectuurrefactoring worden opgelost, niet met `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) tonen het vermogen om herbruikbare componenten te bouwen - `applyDecorators` voegt meerdere Guards samen in één leesbare decorator - Oefen deze vragen met echte code over [NestJS-modules](/technologies/node-nestjs/interview-questions/nestjs-modules-di) en [Interceptors](/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/nl/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture