Colloquio NestJS: Guards, Interceptors e architettura modulare
Domande frequenti nei colloqui tecnici NestJS su Guards, Interceptors e architettura modulare, con esempi concreti in TypeScript e spiegazioni tecniche.

I colloqui tecnici NestJS si concentrano costantemente su tre pilastri del framework: Guards, Interceptors e architettura modulare. Questi meccanismi costituiscono la spina dorsale di ogni applicazione NestJS ben strutturata, e i recruiter li utilizzano per valutare la padronanza reale del framework.
Un Guard controlla l'accesso (chi entra), un Interceptor trasforma i dati (cosa entra ed esce), e l'architettura modulare organizza il tutto. Padroneggiare i tre dimostra una comprensione profonda di NestJS oltre le semplici operazioni CRUD.
Come funzionano i Guards di NestJS e scenari tipici di colloquio
I Guards implementano l'interfaccia CanActivate e decidono se una richiesta raggiunge l'handler. A differenza del middleware, i Guards accedono al contesto di esecuzione di NestJS (ExecutionContext), permettendo decisioni di autorizzazione basate sui metadati dell'handler.
Domanda classica: "Implementare un Guard che verifichi i ruoli utente."
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));
}
}Il Reflector legge i metadati associati dai decoratori personalizzati. Il decoratore @Roles() corrispondente ha questa forma:
import { SetMetadata } from '@nestjs/common';
// Creates a decorator that attaches roles as metadata
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);Una risposta solida in colloquio va oltre il codice. L'ordine di esecuzione conta: Middleware → Guards → Interceptors (prima) → Pipes → Handler → Interceptors (dopo) → Exception Filters. Questa sequenza appare praticamente in ogni colloquio NestJS.
Interceptors di NestJS: trasformare richieste e risposte
Gli Interceptors implementano NestInterceptor e usano RxJS per manipolare il flusso dati prima e dopo l'handler. La loro forza deriva dall'accesso a CallHandler, che rappresenta la pipeline di esecuzione.
Domanda frequente: "Creare un Interceptor di logging che misuri il tempo di esecuzione."
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`);
}),
);
}
}L'operatore tap di RxJS osserva il flusso senza modificarlo. Per trasformare la risposta, l'operatore corretto è 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,
})),
);
}
}Questo pattern di risposta standardizzata fa punteggio nei colloqui: mostra la capacità di pensare al design dell'API e alla coerenza delle risposte.
Middleware: elaborazione HTTP grezza (come Express). Guard: decisione booleana di accesso. Interceptor: trasformazione del flusso richiesta/risposta con RxJS. Confondere questi tre concetti in un colloquio è una red flag immediata.
Architettura modulare di NestJS: costruire applicazioni scalabili
L'architettura modulare distingue gli sviluppatori junior dai senior nei colloqui. NestJS impone un'organizzazione basata su moduli, ma le vere domande puntano alle decisioni architetturali: quando creare un modulo e come gestire le dipendenze tra moduli.
Domanda avanzata: "Come strutturereste un'applicazione e-commerce in NestJS con moduli correttamente disaccoppiati?"
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 {}La trappola classica del colloquio: le dipendenze circolari. Se OrdersModule importa PaymentsModule e PaymentsModule importa OrdersModule, NestJS fallisce all'avvio. La via d'uscita usa forwardRef:
// Resolving circular dependencies
@Module({
imports: [forwardRef(() => PaymentsModule)],
})
export class OrdersModule {}La risposta più forte in colloquio spiega che forwardRef è una soluzione di ripiego, non una vera correzione. La correzione adeguata consiste nel rifattorizzare per estrarre un modulo condiviso (SharedOrderPaymentModule) o usare un Event Emitter per disaccoppiare le comunicazioni.
Pronto a superare i tuoi colloqui su Node.js / NestJS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Dynamic Modules e configurazione avanzata
I Dynamic Modules creano moduli configurabili e riutilizzabili tra progetti. Questo pattern compare nei colloqui di livello senior.
Domanda: "Creare un modulo cache configurabile con opzioni dinamiche."
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],
};
}
}Il servizio corrispondente inietta le opzioni tramite @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;
}
}Il pattern forRoot / forRootAsync è un classico di NestJS. forRootAsync carica la configurazione da un ConfigService iniettabile, che costituisce la pratica raccomandata in produzione.
Marcare un modulo come @Global() sembra comodo, ma abusarne crea accoppiamenti invisibili. Dichiarare in colloquio che @Global() deve essere riservato ai servizi trasversali (config, cache, logger) dimostra maturità architetturale.
Decoratori personalizzati e composizione di Guards
I decoratori personalizzati combinano più Guards e metadati in un'unica annotazione. Questo argomento appare nei ruoli 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),
);
}Utilizzo in un 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);
}
}Il pattern applyDecorators semplifica il codice del controller e centralizza la logica di autorizzazione. Proporre proattivamente questo approccio in colloquio è un segnale forte.
Conclusione
- I Guards usano
CanActivatee ilReflectorper decisioni di accesso basate sui metadati dell'handler - Gli Interceptors sfruttano RxJS (
tap,map) per trasformare il flusso prima e dopo l'handler - L'ordine di esecuzione Middleware → Guards → Interceptors → Pipes → Handler è una domanda quasi universale
- Le dipendenze circolari devono essere risolte tramite refactoring architetturale, non con
forwardRef - I Dynamic Modules (
forRoot/forRootAsync) dimostrano la capacità di costruire componenti riutilizzabili applyDecoratorscompone più Guards in un unico decoratore leggibile- Esercitatevi con queste domande su codice reale nei moduli NestJS e negli Interceptors
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

NestJS + Prisma: lo stack backend moderno per Node.js
Guida completa per costruire un'API backend moderna con NestJS e Prisma. Configurazione, modelli, servizi, transazioni e best practice spiegate.

Domande colloquio Node.js Backend: Guida completa 2026
Le 25 domande più frequenti nei colloqui Node.js backend. Event loop, async/await, stream, clustering e performance spiegate con risposte dettagliate.

NestJS: Creare una REST API completa da zero
Guida passo dopo passo per costruire una REST API pronta per la produzione con NestJS, TypeScript, Prisma e class-validator. CRUD, validazione, gestione errori e interceptor.