En esta página
Interceptores y middleware
El pipeline completo de NestJS
Antes de entrar en interceptores y middleware, es importante entender el orden en que se ejecutan todos los elementos del pipeline de NestJS para una petición HTTP entrante:
Petición HTTP entrante
↓
1. Middleware (Express)
↓
2. Guards (CanActivate)
↓
3. Interceptores (pre-handler)
↓
4. Pipes (transformación y validación)
↓
5. Controlador (handler)
↓
6. Interceptores (post-handler)
↓
7. Exception Filters (si hay error)
↓
Respuesta HTTP salienteLos interceptores son únicos porque envuelven tanto la ejecución pre-handler como la post-handler, lo que los hace perfectos para lógica que necesita "antes y después" del controlador.
¿Qué es un interceptor?
Un interceptor implementa la interfaz NestInterceptor con el método intercept. Este método recibe el ExecutionContext y un CallHandler, y debe retornar un Observable:
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
export class MiInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
console.log('Antes del handler');
return next.handle().pipe(
// aquí puedes transformar, filtrar o hacer side effects
// con los operadores de RxJS
);
}
}next.handle() retorna el Observable que representa la ejecución del método del controlador. Puedes manipularlo con operadores RxJS.
Interceptor de caché de respuestas
import {
Injectable, NestInterceptor, ExecutionContext, CallHandler
} from '@nestjs/common';
import { Observable, of, tap } from 'rxjs';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private readonly cache = new Map<string, { data: unknown; expira: number }>();
private readonly TTL_MS = 30_000; // 30 segundos
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req = context.switchToHttp().getRequest<{ url: string; method: string }>();
if (req.method !== 'GET') {
return next.handle();
}
const cacheKey = req.url;
const entrada = this.cache.get(cacheKey);
if (entrada && entrada.expira > Date.now()) {
return of(entrada.data); // retorna desde caché
}
return next.handle().pipe(
tap((datos: unknown) => {
this.cache.set(cacheKey, {
data: datos,
expira: Date.now() + this.TTL_MS,
});
}),
);
}
}Interceptor de manejo de excepciones personalizado
import {
Injectable, NestInterceptor, ExecutionContext, CallHandler,
HttpException, HttpStatus, Logger,
} from '@nestjs/common';
import { Observable, catchError, throwError } from 'rxjs';
@Injectable()
export class ErrorInterceptor implements NestInterceptor {
private readonly logger = new Logger(ErrorInterceptor.name);
intercept(_ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle().pipe(
catchError((error: unknown) => {
if (error instanceof HttpException) {
// Re-lanza excepciones HTTP tal como están
return throwError(() => error);
}
// Loguea errores inesperados y los convierte en 500
this.logger.error(
'Error inesperado',
error instanceof Error ? error.stack : String(error)
);
return throwError(() =>
new HttpException(
'Error interno del servidor',
HttpStatus.INTERNAL_SERVER_ERROR,
)
);
}),
);
}
}Aplicando interceptores
// Nivel de método
@Get(':id')
@UseInterceptors(CacheInterceptor)
findOne(@Param('id') id: string) {}
// Nivel de controlador
@Controller('productos')
@UseInterceptors(LoggingInterceptor)
export class ProductosController {}
// Nivel global — en main.ts
app.useGlobalInterceptors(
new LoggingInterceptor(),
new TransformResponseInterceptor(),
new TimeoutInterceptor(10_000),
);
// Nivel global con DI — en app.module.ts (recomendado para inyectar deps)
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
{ provide: APP_INTERCEPTOR, useClass: TransformResponseInterceptor },
],
})
export class AppModule {}Middleware de NestJS
Los middlewares en NestJS son equivalentes a los middlewares de Express: funciones que se ejecutan en la cadena de petición-respuesta antes de llegar al sistema de NestJS.
Middleware funcional (más liviano)
// common/middleware/correlation-id.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';
export function correlationIdMiddleware(
req: Request,
res: Response,
next: NextFunction,
): void {
const correlationId = req.headers['x-correlation-id'] as string ?? uuidv4();
req.headers['x-correlation-id'] = correlationId;
res.set('X-Correlation-Id', correlationId);
next();
}Middleware de rate limiting manual
import { Injectable, NestMiddleware, TooManyRequestsException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class RateLimitMiddleware implements NestMiddleware {
private readonly intentos = new Map<string, { count: number; resetEn: number }>();
private readonly MAX_INTENTOS = 100;
private readonly VENTANA_MS = 60_000; // 1 minuto
use(req: Request, _res: Response, next: NextFunction): void {
const ip = req.ip ?? 'unknown';
const ahora = Date.now();
const entrada = this.intentos.get(ip);
if (!entrada || ahora > entrada.resetEn) {
this.intentos.set(ip, { count: 1, resetEn: ahora + this.VENTANA_MS });
return next();
}
if (entrada.count >= this.MAX_INTENTOS) {
throw new TooManyRequestsException(
`Límite de ${this.MAX_INTENTOS} peticiones por minuto excedido`
);
}
entrada.count++;
next();
}
}Aplicando middleware con NestModule
@Module({
imports: [UsuariosModule, ProductosModule, AuthModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(correlationIdMiddleware)
.forRoutes('*');
consumer
.apply(RateLimitMiddleware)
.forRoutes({ path: 'auth/*', method: RequestMethod.POST });
}
}Con interceptores y middleware tienes control completo sobre el ciclo de vida de cada petición en tu aplicación NestJS. En la próxima lección exploraremos una funcionalidad completamente diferente: los WebSockets para comunicación en tiempo real.
Inicia sesión para guardar tu progreso