# NestJS en entretien : Guards, Interceptors et Architecture modulaire > Les questions d'entretien NestJS sur les Guards, Interceptors et l'architecture modulaire, avec des exemples de code concrets et des explications techniques. - Published: 2026-03-30 - Updated: 2026-03-30 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, entretien - Reading time: 9 min --- Les entretiens techniques NestJS couvrent systématiquement trois piliers du framework : les Guards, les Interceptors et l'architecture modulaire. Ces mécanismes constituent le socle de toute application NestJS bien structurée, et les recruteurs s'en servent pour évaluer la maîtrise réelle du framework. > **Ce que les recruteurs évaluent** > > Un Guard contrôle l'accès (qui peut entrer), un Interceptor transforme les données (ce qui entre et sort), et l'architecture modulaire organise le tout. Maîtriser ces trois concepts démontre une compréhension profonde de NestJS au-delà du simple CRUD. ## Fonctionnement des Guards NestJS et cas d'usage en entretien Les Guards implémentent l'interface `CanActivate` et déterminent si une requête peut atteindre le handler. Contrairement aux middleware, ils ont accès au contexte d'exécution NestJS (`ExecutionContext`), ce qui permet des décisions d'autorisation basées sur les métadonnées du handler. Question classique : "Implémenter un Guard qui vérifie les rôles utilisateur." ```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 { // Récupère les rôles définis via le décorateur @Roles() const requiredRoles = this.reflector.getAllAndOverride('roles', [ context.getHandler(), context.getClass(), ]); // Si aucun rôle requis, la route est accessible if (!requiredRoles) return true; // Extrait l'utilisateur depuis la requête HTTP const { user } = context.switchToHttp().getRequest(); // Vérifie que l'utilisateur possède au moins un rôle requis return requiredRoles.some((role) => user.roles?.includes(role)); } } ``` Le `Reflector` est la clé : il lit les métadonnées attachées par les décorateurs personnalisés. Le décorateur `@Roles()` associé ressemble à ceci : ```typescript // roles.decorator.ts import { SetMetadata } from '@nestjs/common'; // Crée un décorateur qui attache les rôles comme métadonnée export const Roles = (...roles: string[]) => SetMetadata('roles', roles); ``` En entretien, la réponse attendue ne s'arrête pas au code. Il faut expliquer l'ordre d'exécution : Middleware → Guards → Interceptors (before) → Pipes → Handler → Interceptors (after) → Exception Filters. Cette séquence revient dans pratiquement tous les entretiens NestJS. ## Interceptors NestJS : transformer les requêtes et réponses Les Interceptors implémentent `NestInterceptor` et utilisent RxJS pour manipuler le flux de données avant et après le handler. Leur puissance réside dans l'accès au `CallHandler`, qui représente le pipeline d'exécution. Question fréquente : "Créer un Interceptor de logging qui mesure le temps d'exécution." ```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() appelle le handler de la route return next.handle().pipe( tap(() => { // Exécuté APRÈS la réponse du handler const duration = Date.now() - now; this.logger.log(`${method} ${url} — ${duration}ms`); }), ); } } ``` L'opérateur `tap` de RxJS observe le flux sans le modifier. Pour transformer la réponse, `map` est l'opérateur adapté : ```typescript // transform.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; // Interface générique pour une réponse API standardisée 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, })), ); } } ``` Ce pattern de réponse standardisée est très apprécié en entretien : il montre la capacité à penser "API design" et cohérence des réponses. > **Guard vs Interceptor vs Middleware** > > Middleware : traitement HTTP brut (comme Express). Guard : décision booléenne d'accès. Interceptor : transformation du flux requête/réponse avec RxJS. En entretien, confondre ces trois concepts est un signal négatif immédiat. ## Architecture modulaire NestJS : organiser une application scalable L'architecture modulaire est le sujet qui distingue les développeurs juniors des seniors en entretien. NestJS impose une organisation en modules, mais la vraie question porte sur les décisions architecturales : quand créer un module ? Comment gérer les dépendances entre modules ? Question avancée : "Comment structurer une application e-commerce NestJS avec des modules bien découplés ?" ```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({ // Importe les modules dont OrdersModule dépend imports: [PaymentsModule, ProductsModule], controllers: [OrdersController], providers: [OrdersService], // Expose OrdersService pour les modules qui importent OrdersModule exports: [OrdersService], }) export class OrdersModule {} ``` Le piège classique en entretien : les dépendances circulaires. Si `OrdersModule` importe `PaymentsModule` et `PaymentsModule` importe `OrdersModule`, NestJS échoue au démarrage. La solution utilise `forwardRef` : ```typescript // Résolution des dépendances circulaires @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Mais la meilleure réponse en entretien consiste à expliquer que `forwardRef` est un contournement : la vraie solution est de refactorer pour extraire un module partagé (`SharedOrderPaymentModule`) ou d'utiliser un Event Emitter pour découpler les communications. ## Dynamic Modules et configuration avancée Les Dynamic Modules permettent de créer des modules configurables, réutilisables entre projets. Ce pattern apparaît dans les entretiens pour les postes senior. Question : "Créer un module de cache configurable avec des options dynamiques." ```typescript // cache/cache.module.ts import { Module, DynamicModule, Global } from '@nestjs/common'; import { CacheService } from './cache.service'; // Interface de configuration du module export interface CacheModuleOptions { ttl: number; // Durée de vie en secondes maxItems: number; // Nombre max d'entrées prefix: string; // Préfixe des clés } @Global() // Disponible partout sans import explicite @Module({}) export class CacheModule { static forRoot(options: CacheModuleOptions): DynamicModule { return { module: CacheModule, providers: [ { // Injecte les options via un token personnalisé provide: 'CACHE_OPTIONS', useValue: options, }, CacheService, ], exports: [CacheService], }; } } ``` Le service correspondant injecte les options 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); // Vérifie l'expiration avant de retourner if (!entry || entry.expiry < Date.now()) return null; return entry.value as T; } } ``` Le pattern `forRoot` / `forRootAsync` est un classique NestJS. `forRootAsync` permet de charger la configuration depuis un `ConfigService` injectable, ce qui est la pratique recommandée en production. > **Piège d'entretien : @Global()** > > Marquer un module `@Global()` semble pratique, mais en abuser crée un couplage invisible. En entretien, mentionner que `@Global()` doit être réservé aux services transversaux (config, cache, logger) démontre une maturité architecturale. ## Custom Decorators et composition de Guards Les décorateurs personnalisés combinent plusieurs Guards et métadonnées en une seule annotation. Ce sujet revient pour les postes mid-senior. ```typescript // auth.decorator.ts import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common'; import { JwtAuthGuard } from './jwt-auth.guard'; import { RolesGuard } from './roles.guard'; // Combine authentification JWT + vérification des rôles export function Auth(...roles: string[]) { return applyDecorators( SetMetadata('roles', roles), UseGuards(JwtAuthGuard, RolesGuard), ); } ``` Utilisation dans un contrôleur : ```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') // Un seul décorateur remplace UseGuards + Roles findAll() { return this.ordersService.findAll(); } @Post() @Auth('admin') create(@Body() dto: CreateOrderDto) { return this.ordersService.create(dto); } } ``` Ce pattern `applyDecorators` simplifie le code des contrôleurs et centralise la logique d'autorisation. En entretien, proposer spontanément cette approche est un signal fort. ## Conclusion - Les Guards utilisent `CanActivate` et le `Reflector` pour des décisions d'accès basées sur les métadonnées des handlers - Les Interceptors exploitent RxJS (`tap`, `map`) pour transformer le flux avant et après le handler - L'ordre d'exécution Middleware → Guards → Interceptors → Pipes → Handler est une question quasi-systématique - Les dépendances circulaires se résolvent par refactoring architectural, pas par `forwardRef` - Les Dynamic Modules (`forRoot`/`forRootAsync`) démontrent la capacité à créer des composants réutilisables - `applyDecorators` compose plusieurs Guards en un décorateur unique et lisible - Pratiquer ces questions avec du code réel sur [les modules NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) et [les Interceptors](/technologies/node-nestjs/interview-questions/middleware-interceptors) renforce la préparation --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/node-nestjs/nestjs-guards-interceptors-architecture-modulaire