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.

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.
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."
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));
}
}De Reflector leest de metadata die door custom decorators is gekoppeld. De bijbehorende @Roles()-decorator ziet er zo uit:
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."
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`);
}),
);
}
}De RxJS-operator tap observeert de stream zonder hem te wijzigen. Om de response te transformeren is map de juiste operator:
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,
})),
);
}
}Dit gestandaardiseerde response-patroon scoort goed in een gesprek: het toont het vermogen om na te denken over API-ontwerp en consistentie van responses.
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?"
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:
// 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.
Klaar om je Node.js / NestJS gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
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."
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():
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;
}
}Het patroon forRoot / forRootAsync is een NestJS-klassieker. forRootAsync laadt de configuratie vanuit een injecteerbare ConfigService, wat de aanbevolen praktijk in productie is.
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.
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:
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
CanActivateen deReflectorvoor 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 applyDecoratorsvoegt meerdere Guards samen in één leesbare decorator- Oefen deze vragen met echte code over NestJS-modules en Interceptors
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

NestJS + Prisma: de moderne backend-stack voor Node.js
Volledige gids voor het bouwen van een moderne backend-API met NestJS en Prisma. Setup, modellen, services, transacties en best practices uitgelegd.

Node.js Backend Sollicitatievragen: Volledige Gids 2026
De 25 meest gestelde Node.js backend sollicitatievragen. Event loop, async/await, streams, clustering en performance uitgelegd met gedetailleerde antwoorden.

NestJS: Een complete REST API vanaf nul bouwen
Stap-voor-stap handleiding voor het bouwen van een productieklare REST API met NestJS, TypeScript, Prisma en class-validator. CRUD, validatie, foutafhandeling en interceptors.