
Interceptores
Los interceptores en NestJS son una característica fundamental que permite interceptar y transformar las peticiones HTTP antes de que lleguen a los controladores, así como las respuestas antes de que se envíen al cliente. Funcionan de manera similar a los middlewares, pero con capacidades más avanzadas y específicas para el contexto de autenticación y autorización.
Un interceptor implementa la interfaz NestInterceptor y utiliza el patrón de programación orientada a aspectos (AOP) para separar las preocupaciones transversales del código de negocio principal.
Creación de un interceptor básico
Para crear un interceptor, necesitas implementar la interfaz NestInterceptor y definir el método intercept. Este método recibe el contexto de ejecución y el siguiente manejador en la cadena:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
console.log(`Petición entrante: ${method} ${url}`);
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`Tiempo de respuesta: ${Date.now() - now}ms`))
);
}
}
El método intercept debe devolver un Observable que representa la respuesta del controlador. El parámetro next.handle() ejecuta el siguiente interceptor o el controlador final si no hay más interceptores en la cadena.
Interceptor para autenticación
En el contexto de autenticación, los interceptores son especialmente útiles para validar tokens, extraer información del usuario y añadir datos al contexto de la petición:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthInterceptor implements NestInterceptor {
constructor(private jwtService: JwtService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('Token no encontrado');
}
try {
const payload = this.jwtService.verify(token);
// Añadir información del usuario al request
request.user = payload;
} catch (error) {
throw new UnauthorizedException('Token inválido');
}
return next.handle();
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
Este interceptor extrae el token JWT del header Authorization, lo valida y añade la información del usuario al objeto request para que esté disponible en los controladores.
Transformación de respuestas
Los interceptores también pueden transformar las respuestas antes de enviarlas al cliente. Esto es útil para estandarizar el formato de las respuestas o añadir metadatos:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(data => ({
success: true,
timestamp: new Date().toISOString(),
data: data,
statusCode: context.switchToHttp().getResponse().statusCode
}))
);
}
}
Aplicación de interceptores
Los interceptores se pueden aplicar a diferentes niveles de la aplicación:
A nivel de controlador:
import { Controller, Get, UseInterceptors } from '@nestjs/common';
@Controller('users')
@UseInterceptors(AuthInterceptor)
export class UsersController {
@Get()
findAll() {
return 'Lista de usuarios';
}
}
A nivel de método específico:
@Controller('users')
export class UsersController {
@Get()
@UseInterceptors(LoggingInterceptor, ResponseInterceptor)
findAll() {
return 'Lista de usuarios';
}
}
A nivel global en el módulo:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: AuthInterceptor,
},
],
})
export class AppModule {}
Interceptor con manejo de errores
Los interceptores pueden capturar y manejar errores que ocurren durante la ejecución de los controladores:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, HttpException } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(error => {
console.error('Error capturado por interceptor:', error.message);
if (error instanceof HttpException) {
return throwError(() => error);
}
// Transformar errores no HTTP en respuestas estructuradas
return throwError(() => new HttpException('Error interno del servidor', 500));
})
);
}
}
Los interceptores proporcionan una forma elegante y reutilizable de implementar funcionalidades transversales como autenticación, logging, transformación de datos y manejo de errores, manteniendo el código de los controladores limpio y enfocado en la lógica de negocio específica.
Cadena del pipeline: middleware, guards, interceptors (pre), pipes, handler, interceptors (post), exception filters
El interceptor envuelve next.handle(): el código antes del next.handle() se ejecuta antes del handler (pre); las operaciones RxJS encadenadas (tap, map, catchError) se ejecutan después, sobre el observable que devuelve la respuesta. La secuencia completa por petición HTTP es: middleware global, guards (CanActivate), interceptors pre, pipes (ValidationPipe, ParseIntPipe), handler del controlador, interceptors post y exception filters como último filtro antes del cliente. Cualquier excepción lanzada en cualquier eslabón cae al exception filter correspondiente.
Interceptor de timeout
Para evitar handlers que cuelguen indefinidamente (bug de un servicio externo, deadlock de BD), un interceptor de timeout aborta el observable y devuelve 503:
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(_: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle().pipe(
timeout(10000),
catchError((err) =>
err instanceof TimeoutError
? throwError(() => new RequestTimeoutException('Request demasiado lenta'))
: throwError(() => err),
),
);
}
}
Interceptor de cache
@nestjs/cache-manager ya incluye CacheInterceptor, pero es útil entender el patrón. Una versión simple con LRU:
@Injectable()
export class SimpleCacheInterceptor implements NestInterceptor {
private readonly cache = new LRUCache<string, unknown>({ max: 500, ttl: 60_000 });
intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
const req = ctx.switchToHttp().getRequest();
if (req.method !== 'GET') return next.handle();
const key = `${req.url}|${req.user?.tenantId ?? 'anon'}`;
const hit = this.cache.get(key);
if (hit !== undefined) return of(hit);
return next.handle().pipe(tap((data) => this.cache.set(key, data)));
}
}
La clave incluye tenantId para evitar que un usuario lea datos cacheados de otro tenant. Es un error muy frecuente cuando se aplica caché sin pensar en multi-tenancy.
Interceptor de Sentry / observabilidad
Para enviar errores y métricas a APM:
@Injectable()
export class SentryInterceptor implements NestInterceptor {
intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
const tx = Sentry.startTransaction({
name: `${ctx.getClass().name}.${ctx.getHandler().name}`,
op: 'http.server',
});
return next.handle().pipe(
tap(() => tx.setStatus('ok')),
catchError((err) => {
tx.setStatus('internal_error');
Sentry.captureException(err);
return throwError(() => err);
}),
finalize(() => tx.finish()),
);
}
}
Tests de interceptor
Los interceptores se prueban con Test.createTestingModule registrando un módulo dummy con un controlador y verificando el comportamiento end-to-end con supertest. Es la forma de cubrir tap (cuando se ejecuta), catchError (transformación de errores) y map (transformación de respuesta) sin acoplarse a internals.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Nest
Documentación oficial de Nest
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Nest es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Nest
Explora más contenido relacionado con Nest y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender qué son los interceptores en NestJS y su función principal. Aprender a crear interceptores básicos implementando la interfaz NestInterceptor. Utilizar interceptores para validar tokens JWT y gestionar autenticación. Aplicar interceptores para transformar respuestas antes de enviarlas al cliente. Implementar interceptores para capturar y manejar errores en la aplicación.