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`)),
);
}
}
In the example above:
- We create a
LoggingInterceptor
class that implements theNestInterceptor
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();
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,
})),
);
}
}
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' }];
}
}
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)
Perhaps, instead of
console.log()
, it is better to show the use of a logger that can be used in real apps.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".
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.