What Are Interceptors in NestJS?
An interceptor is a class that implements the NestInterceptor interface and uses the intercept() method to:
- execute code before a handler runs,
- transform the result returned by the handler,
- execute logic after the handler finishes,
- completely override request handling if necessary.
Interceptors wrap around route handlers using the Ascpect-Oriented Programming (AOP) pattern.
Think of them like a middleware with superpowers.
Why Interceptors Exist
Without interceptors, repetitive logic gets scattered everywhere.
Imagine writing this in every controller:
console.log('Request started');
const start = Date.now();
const result = await this.userService.findAll();
console.log(`Completed in ${Date.now() - start}ms`);
return {
success: true,
data: result,
};This creates:
- duplicated code,
- poor maintainability,
- inconsistent API structure,
- tight coupling.
Interceptors solves this by centralizing cross-cutting concerns.
What Problems Do Interceptors Solve?
Interceptors are commonly used for:
| Use Case | Example |
|---|---|
| Logging | Request/response logging |
| Response Mapping | Standard API response format |
| Serialization | Hide passwords from responses |
| Performance Monitoring | Execution timing |
| Caching | Return cached data |
| Error Mapping | Transform exceptions |
| Retry Logic | Retry failed operations |
| Data Transformation | Modify outgoing responses |
| Auditing | User action tracking |
| Compression | Transform response streams |
Position of Interceptors in Request Lifecycle
NestJS request lifecycle:
Incoming Request
↓
Middleware
↓
Guards
↓
Interceptors (before)
↓
Pipes
↓
Controller Route Handler
↓
Service
↓
Interceptors (after)
↓
Exception Filters
↓
Outgoing ResponseImportant:
- Interceptors execute both before and after route handlers.
- This makes them different from middleware.
Middleware vs Guards vs Pipes vs Interceptors
| Feature | Middleware | Guards | Pipes | Interceptors |
|---|---|---|---|---|
| Runs Before Handler | Yes | Yes | Yes | Yes |
| Runs After Handler | No | No | No | Yes |
| Access Response | Limited | No | No | Yes |
| Transform Response | No | No | No | Yes |
| Authorization | Sometimes | Best choice | No | Not ideal |
| Validation | No | No | Yes | No |
| Logging | Basic | No | No | Excellent |
Basic Structure of an Interceptor
A custom interceptor:
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> {
console.log('Before handler');
const now = Date.now();
return next.handle().pipe(
tap(() => console.log(`After handler ${Date.now() - now}ms`)),
);
}
}Understanding the intercept() Method
The core of interceptors is:
intercept(context: ExecutionContext, next: CallHandler)1 ExecutionContext:
Provides details about the current execution environment.
You can access:
- request object,
- response object,
- route handler,
- controller class,
- GraphQL context,
- WebSocket context.
Example:
const request = context.switchToHttp().getRequest();2 CallHandler
Represents the next step in the request pipeline.
next.handler()This executes:
- pipes,
- controller method,
- service logic.
It returns an RxJS Observable.
Why Observable Are Used
NestJS heavily integrates with RxJS.
The response stream is treated as an Observable.
This allows interceptors to:
- transform data,
- retry requests,
- catch errors,
- delay responses,
- map streams.
Internal Working of Interceptors
Conceptually:
Interceptor Before
↓
Controller Handler
↓
Observable Stream
↓
Interceptor AfterExample:
return next.handle().pipe(
map(data => ({
success: true,
data,
})),
);This interceptor intercepts the returned Observable and modifies its output.
Response Transformation Interceptor
One of the most common use cases.
Example:
Without interceptor:
{
"id": 1,
"name": "John"
}With interceptor:
{
"success": true,
"data": {
"id": 1,
"name": "John"
}
}Implementation:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
map(data => ({
success: true,
message: 'Request successful',
data,
})),
);
}
}Logging Interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
console.log(`${request.method} ${request.url}`);
const now = Date.now();
return next.handle().pipe(
tap(() => {
console.log(`Completed in ${Date.now() - now}ms`);
}),
);
}
}Timeout Interceptor
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
RequestTimeoutException,
} from '@nestjs/common';
import { timeout, catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
timeout(5000),
catchError(err => {
return throwError(
() => new RequestTimeoutException(),
);
}),
);
}
}Exception Mapping Interceptor
return next.handle().pipe(
catchError(err => {
throw new BadRequestException('Custom error');
}),
);Caching Interceptor
Interceptors can return cached data before handler execution.
if (cacheExists) {
return of(cachedData);
}
return next.handle();Built-in Interceptors in NestJS
NestJS provides several built-in interceptors.
1 ClassSerializerInterceptor
Used with class-transformer
Helps:
- exclude fields,
- expose properties,
- transform entities.
Example:
@Exclude()
password: string;Usage:
app.useGlobalInterceptors(
new ClassSerializerInterceptor(app.get(Reflector)),
);2 CacheInterceptor
Provides automatic response caching.
@UseInterceptors(CacheInterceptor)3 FileInterceptors
Used for file uploads with Multer.
Examples:
FileInterceptorFilesInterceptorAnyFilesInterceptor
Leave a comment
Your email address will not be published. Required fields are marked *
