What are interceptors in NestJS?
Interceptors in NestJS
Interceptors wrap the execution of a route handler and can:
- Execute logic before and/or after a handler
- Transform the result returned by a handler
- Transform exceptions
- Override or extend basic handler behavior (e.g., cache)
- Measure execution time
They implement the NestInterceptor interface and use RxJS Observables.
Interceptor Structure
typescript
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 now = Date.now();
return next
.handle() // calls the route handler
.pipe(
tap(() => console.log(`Elapsed: ${Date.now() - now}ms`))
);
}
}Common Interceptor Patterns
1. Response Transform (Wrap in envelope)
typescript
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
intercept(context: ExecutionContext, next: CallHandler): Observable<{ data: T }> {
return next.handle().pipe(
map(data => ({ data, success: true, timestamp: new Date().toISOString() }))
);
}
}
// Result: { "data": {...}, "success": true, "timestamp": "..." }2. Caching
typescript
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private cacheService: CacheService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const key = context.switchToHttp().getRequest().url;
const cached = await this.cacheService.get(key);
if (cached) return of(cached); // return from cache, skip handler
return next.handle().pipe(
tap(result => this.cacheService.set(key, result, 60))
);
}
}3. Execution Timing
typescript
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
const req = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} — ${duration}ms`);
})
);
}
}4. Exception Mapping
typescript
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(err =>
throwError(() => new BadGatewayException('External service error'))
)
);
}
}Applying Interceptors
typescript
// Method-level
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() { ... }
// Controller-level
@Controller('users')
@UseInterceptors(TransformInterceptor)
export class UsersController {}
// Global (in module, supports DI)
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
],
})NestJS Request Lifecycle
Incoming Request
↓
Middleware
↓
Guards (can reject)
↓
Interceptors (before)
↓
Pipes (transform/validate)
↓
Route Handler
↓
Interceptors (after)
↓
Exception Filters (if error)
↓
Outgoing ResponseSummary
Interceptors are powerful tools for cross-cutting concerns — wrapping all responses in a consistent envelope, logging, caching, timing, and error mapping. Use RxJS map, tap, and catchError operators to react to handler execution and transform results.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.