# NestJS-Interview: Guards, Interceptors und modulare Architektur > Häufige NestJS-Interviewfragen zu Guards, Interceptors und modularer Architektur mit konkreten TypeScript-Codebeispielen und technischen Erklärungen. - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- Technische NestJS-Interviews konzentrieren sich konsequent auf drei Säulen des Frameworks: Guards, Interceptors und modulare Architektur. Diese Mechanismen bilden das Rückgrat jeder gut strukturierten NestJS-Anwendung, und Recruiter nutzen sie, um die tatsächliche Beherrschung des Frameworks zu bewerten. > **Was Interviewer bewerten** > > Ein Guard kontrolliert den Zugriff (wer reinkommt), ein Interceptor transformiert Daten (was rein- und rausgeht), und die modulare Architektur organisiert alles. Die Beherrschung aller drei zeigt ein tiefes NestJS-Verständnis jenseits einfacher CRUD-Operationen. ## Wie NestJS-Guards funktionieren und typische Interview-Szenarien Guards implementieren das Interface `CanActivate` und entscheiden, ob ein Request den Handler erreicht. Im Gegensatz zu Middleware greifen Guards auf den Ausführungskontext von NestJS (`ExecutionContext`) zu, wodurch Autorisierungsentscheidungen anhand von Handler-Metadaten möglich werden. Klassische Frage: "Implementieren Sie einen Guard, der die Benutzerrollen prüft." ```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)); } } ``` Der `Reflector` liest die Metadaten, die von benutzerdefinierten Decorators angehängt wurden. Der zugehörige `@Roles()`-Decorator sieht so aus: ```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); ``` Eine starke Interview-Antwort geht über den Code hinaus. Die Ausführungsreihenfolge ist entscheidend: Middleware → Guards → Interceptors (vorher) → Pipes → Handler → Interceptors (nachher) → Exception Filters. Diese Reihenfolge erscheint in praktisch jedem NestJS-Interview. ## NestJS-Interceptors: Requests und Responses transformieren Interceptors implementieren `NestInterceptor` und nutzen RxJS, um den Datenstrom vor und nach dem Handler zu manipulieren. Ihre Stärke liegt im Zugriff auf `CallHandler`, der die Ausführungspipeline repräsentiert. Häufige Frage: "Erstellen Sie einen Logging-Interceptor, der die Ausführungszeit misst." ```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`); }), ); } } ``` Der RxJS-Operator `tap` beobachtet den Stream, ohne ihn zu verändern. Um die Response zu transformieren, ist `map` der richtige 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, })), ); } } ``` Dieses standardisierte Response-Pattern punktet im Interview: Es zeigt die Fähigkeit, über API-Design und Response-Konsistenz nachzudenken. > **Guard vs Interceptor vs Middleware** > > Middleware: rohe HTTP-Verarbeitung (wie in Express). Guard: boolesche Zugriffsentscheidung. Interceptor: Transformation des Request/Response-Streams mit RxJS. Diese drei Konzepte im Interview zu verwechseln ist eine sofortige Red Flag. ## Modulare NestJS-Architektur: skalierbare Anwendungen bauen Die modulare Architektur trennt im Interview Junior- von Senior-Entwicklern. NestJS erzwingt eine modulbasierte Organisation, aber die echten Fragen zielen auf architektonische Entscheidungen: wann ein Modul erstellt werden sollte und wie modulübergreifende Abhängigkeiten verwaltet werden. Fortgeschrittene Frage: "Wie würden Sie eine NestJS-E-Commerce-Anwendung mit sauber entkoppelten Modulen strukturieren?" ```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 {} ``` Die klassische Interview-Falle: zirkuläre Abhängigkeiten. Wenn `OrdersModule` `PaymentsModule` importiert und `PaymentsModule` `OrdersModule` importiert, schlägt NestJS beim Start fehl. Der Notausgang nutzt `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Die stärkste Interview-Antwort erklärt, dass `forwardRef` ein Workaround und keine Lösung ist. Die korrekte Behebung besteht darin, ein gemeinsames Modul (`SharedOrderPaymentModule`) auszulagern oder einen Event Emitter einzusetzen, um die Kommunikation zu entkoppeln. ## Dynamic Modules und fortgeschrittene Konfiguration Dynamic Modules erstellen konfigurierbare, projektübergreifend wiederverwendbare Module. Dieses Pattern erscheint in Senior-Interviews. Frage: "Erstellen Sie ein konfigurierbares Cache-Modul mit dynamischen Optionen." ```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], }; } } ``` Der zugehörige Service injiziert die Optionen über `@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; } } ``` Das Pattern `forRoot` / `forRootAsync` ist ein NestJS-Klassiker. `forRootAsync` lädt die Konfiguration aus einem injizierbaren `ConfigService`, was die empfohlene Produktionspraxis darstellt. > **Interview-Falle: @Global()** > > Ein Modul als `@Global()` zu kennzeichnen wirkt bequem, aber übermäßige Nutzung erzeugt unsichtbare Kopplung. Im Interview zu erwähnen, dass `@Global()` für querschnittliche Services (Config, Cache, Logger) reserviert sein sollte, zeigt architektonische Reife. ## Custom Decorators und Guard-Komposition Custom Decorators kombinieren mehrere Guards und Metadaten in einer einzigen Annotation. Dieses Thema kommt für Mid-Senior-Positionen vor. ```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), ); } ``` Verwendung in einem 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); } } ``` Das `applyDecorators`-Pattern vereinfacht den Controller-Code und zentralisiert die Autorisierungslogik. Diesen Ansatz proaktiv im Interview vorzuschlagen ist ein starkes Signal. ## Fazit - Guards nutzen `CanActivate` und den `Reflector` für Zugriffsentscheidungen anhand von Handler-Metadaten - Interceptors setzen RxJS (`tap`, `map`) ein, um den Stream vor und nach dem Handler zu transformieren - Die Ausführungsreihenfolge Middleware → Guards → Interceptors → Pipes → Handler ist eine nahezu universelle Frage - Zirkuläre Abhängigkeiten sollten durch architektonisches Refactoring gelöst werden, nicht durch `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) zeigen die Fähigkeit, wiederverwendbare Komponenten zu bauen - `applyDecorators` kombiniert mehrere Guards in einem einzigen, lesbaren Decorator - Üben Sie diese Fragen mit echtem Code zu [NestJS-Modulen](/technologies/node-nestjs/interview-questions/nestjs-modules-di) und [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/de/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture