Mastering Cross-Cutting Concerns in NestJS with Interceptors

Akshay Joshi - Aug 9 - - Dev Community

When building a NestJS application, you often encounter situations where you need to apply the same logic across multiple routes or even the entire application. This is where Interceptors come in handy. They allow you to handle cross-cutting concerns like logging, error handling, or response transformation in a clean and reusable way.

In this post, we'll explore how to use interceptors in NestJS, with practical examples and code snippets to help you implement them effectively.


What Are Interceptors?

Interceptors in NestJS are classes annotated with the @Injectable() decorator that implement the NestInterceptor interface. They provide a way to intercept requests before they reach your route handlers and responses before they are sent to the client.

Interceptors are particularly useful for:

  • Logging
  • Transforming responses
  • Handling errors consistently
  • Modifying request data
  • Implementing performance monitoring

Creating a Simple Logging Interceptor

Let's start by creating a basic logging interceptor that logs the details of every incoming request.

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();
    const request = context.switchToHttp().getRequest();
    console.log(`Incoming request: ${request.method} ${request.url}`);

    return next
      .handle()
      .pipe(
        tap(() => console.log(`Request handled in ${Date.now() - now}ms`)),
      );
  }
}
Enter fullscreen mode Exit fullscreen mode

In the example above:

  • We create a LoggingInterceptor class that implements the NestInterceptor interface.
  • The intercept() method is where the logic happens. We log the incoming request and calculate the time taken to handle the request.

Applying the Interceptor Globally

To make sure that the interceptor is applied to all requests across the application, you can add it to the 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);
  app.useGlobalInterceptors(new LoggingInterceptor());
  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

By using app.useGlobalInterceptors(new LoggingInterceptor());, you ensure that the logging logic is applied to every request that hits your application.

Transforming Responses with Interceptors

Interceptors can also be used to transform the response data before it reaches the client. Let's create an example where we wrap every response in a consistent structure:

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

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({
        statusCode: context.switchToHttp().getResponse().statusCode,
        data,
      })),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

This TransformInterceptor ensures that every response from your controllers is wrapped in an object containing the statusCode and data.

Using the TransformInterceptor in a Specific Route

While you can apply interceptors globally, you might want to use them for specific routes or controllers. Here’s how you can do it:

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

@Controller('users')
export class UsersController {

  @Get()
  @UseInterceptors(TransformInterceptor)
  findAll() {
    return [{ id: 1, name: 'John Doe' }];
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the TransformInterceptor will only be applied to the findAll route in the UsersController.


Conclusion

Interceptors in NestJS are powerful tools that can help you manage cross-cutting concerns effectively. Whether you're logging requests, transforming responses, or handling errors consistently, interceptors provide a clean and reusable way to keep your codebase organized.

By mastering interceptors, you can ensure that your application is not only robust and maintainable but also scalable as your project grows.


Discussion

Have you implemented interceptors in your NestJS applications? What challenges did you face, and how did you overcome them? Feel free to share your experiences, tips, and questions in the comments below. Let’s learn together!

Happy Nesting!!!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .