Updated on 18 May, 202622 mins read 15 views

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 CaseExample
LoggingRequest/response logging
Response MappingStandard API response format
SerializationHide passwords from responses
Performance MonitoringExecution timing
CachingReturn cached data
Error MappingTransform exceptions
Retry LogicRetry failed operations
Data TransformationModify outgoing responses
AuditingUser action tracking
CompressionTransform 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 Response

Important:

  • Interceptors execute both before and after route handlers.
  • This makes them different from middleware.

Middleware vs Guards vs Pipes vs Interceptors

FeatureMiddlewareGuardsPipesInterceptors
Runs Before HandlerYesYesYesYes
Runs After HandlerNoNoNoYes
Access ResponseLimitedNoNoYes
Transform ResponseNoNoNoYes
AuthorizationSometimesBest choiceNoNot ideal
ValidationNoNoYesNo
LoggingBasicNoNoExcellent

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 After

Example:

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:

  • FileInterceptor
  • FilesInterceptor
  • AnyFilesInterceptor

 

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *