Progressive Full Stack Application Development with Live Projects

Nest JS

Interceptors in Nest JS

In NestJS, interceptors are powerful tools that allow you to add extra behavior to the lifecycle of a request-response cycle. They can be used for a variety of tasks, such as logging, transforming responses, adding headers, caching, or even handling cross-cutting concerns like error handling.

Key Concepts of Interceptors

  • Before Execution: Interceptors can run code before the handler (controller method) is called.
  • After Execution: They can also run code after the handler executes and can manipulate the result or modify the response.
  • Transformation: Interceptors can modify the response data before it’s sent to the client.
  • Exception Handling: You can use interceptors to catch and handle exceptions.

Types of Interceptors in NestJS

  • Method-level interceptors: These are applied only to specific methods in a controller.
  • Global interceptors: These apply globally to all methods in the application, which can be set in the main application file (main.ts).

Basic Structure of an Interceptor

An interceptor is a class that implements the NestInterceptor interface and defines the intercept method. The intercept method takes two arguments:

  • context: It contains metadata about the current request.
  • next: The ExecutionContext has methods to proceed to the next handler in the pipeline.
				
					import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class MyInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable {
    console.log('Before request handling');
    const now = Date.now();

    // Handling the request
    return next
      .handle() // Proceed to the next handler (controller method)
      .pipe(
        tap(() => console.log(`After request handling, elapsed time: ${Date.now() - now}ms`))
      );
  }
}

				
			

Key Components

  • ExecutionContext: This provides details about the current request, such as the handler, the class, the HTTP request object, etc.
  • CallHandler: This represents the execution of the method. next.handle() returns an Observable that allows you to manipulate the result.

Example 1: Logging Interceptor

A simple logging interceptor can be used to log the time taken by the request handler

				
					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 {
    const now = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`Request handled in ${Date.now() - now}ms`)),
    );
  }
}

				
			

This interceptor logs the time it took for the controller method to handle the request.

Example 2: Transforming Response Data

Interceptors can also be used to transform the response data before it is sent back to the client. For example, if you want to wrap all responses in a standard format (e.g., data, status):

				
					import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ResponseTransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable {
    return next.handle().pipe(
      map((data) => ({
        status: 'success',
        data: data,
      })),
    );
  }
}

				
			

In this example, all responses will be wrapped in a status and data object, like

				
					{
  "status": "success",
  "data": { ... }
}

				
			

Applying Interceptors

1. Method-Level Interceptor

You can apply an interceptor to a specific method in your controller by using the @UseInterceptors() decorator

				
					import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';

@Controller('cats')
export class CatsController {
  @Get()
  @UseInterceptors(LoggingInterceptor)
  findAll() {
    return ['Cat 1', 'Cat 2'];
  }
}

				
			

In this case, the LoggingInterceptor will only apply to the findAll method in the CatsController.

2. Global Interceptor

To apply an interceptor globally, you can use app.useGlobalInterceptors() in your main.ts file

				
					import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './logging.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Apply the LoggingInterceptor globally
  app.useGlobalInterceptors(new LoggingInterceptor());

  await app.listen(3000);
}
bootstrap();

				
			

Now, the LoggingInterceptor will be applied to every request in the application

Example 3: Caching Interceptor

You can implement a caching mechanism in an interceptor to prevent repeated calls for the same resource. Here’s a simple example:

				
					import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  private cache = new Map();

  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const key = context.switchToHttp().getRequest().url;
    
    // If data is already in cache, return it
    if (this.cache.has(key)) {
      return new Observable(observer => {
        observer.next(this.cache.get(key));
        observer.complete();
      });
    }

    // Otherwise, call the next handler and cache the result
    return next.handle().pipe(
      map((data) => {
        this.cache.set(key, data);
        return data;
      }),
    );
  }
}

				
			

This interceptor caches the response for each URL and reuses the cached data if the same request is made again.

Conclusion

  • Interceptors in NestJS allow you to control the flow of requests and responses, modify or transform data, and apply cross-cutting concerns like logging or caching.
  • You can create interceptors by implementing the NestInterceptor interface and using the intercept() method.
  • Interceptors can be applied either globally (using app.useGlobalInterceptors()) or on specific methods (@UseInterceptors() decorator).
  • They provide a clean and modular way to handle concerns that cut across multiple parts of your application.