DEV Community

Cover image for Mastering Cross-Cutting Concerns in NestJS with Interceptors
Akshay Joshi
Akshay Joshi

Posted on

Mastering Cross-Cutting Concerns in NestJS with Interceptors

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!!!

Top comments (2)

Collapse
 
kostyatretyak profile image
Костя Третяк

Perhaps, instead of console.log(), it is better to show the use of a logger that can be used in real apps.

What challenges did you face, and how did you overcome them?

Currently (v10.3.10), NestJS does not allow exporting so-called "enhancers", in particular interceptors. Because of that you can't just import a module with interceptors to automatically use them in the current module. Therefore, the concepts of "scalability" and "modularity" in this case do not fit well with the NestJS framework.

What do I do to solve this problem? I just use Ditsmod, which can export so-called "enhancers".

Collapse
 
doozieakshay profile image
Akshay Joshi

Thank you for bringing up such an insightful point!

Console.log is mainly for debugging; you can integrate winston or pino, which both provide structured logging and support various log levels.

its good to explore alternatives and find the solution for the job, and your experience with Ditsmod is a perfect example of that.

I appreciate you sharing this workaround—it adds to the conversation and might be helpful for others facing similar challenges in their NestJS projects.