Interceptors: Transforming Responses
Creating interceptors to transform responses before they are sent to the client, e.g., adding metadata or formatting the data.
NestJS Interceptors: Global and Local
Introduction to Interceptors in NestJS
Interceptors in NestJS are a powerful feature that allows you to intercept and transform requests and responses. They can be used for a variety of purposes, including logging, caching, exception handling, and request validation. They are similar to middleware, but offer greater flexibility and control over the execution pipeline within the controller.
Applying Interceptors Globally
Applying interceptors globally means they will be executed for every route in your NestJS application. This is useful for tasks like global logging, setting default headers, or global exception handling.
How to apply an interceptor globally:
- Create an Interceptor: Define your interceptor class, implementing the
NestInterceptor
interface and theintercept()
method. - Register the Interceptor in the Module: Use the
APP_INTERCEPTOR
provider in your application module (usuallyAppModule
) to register the interceptor globally.
Example:
1. Create a Simple Logging Interceptor
// src/interceptors/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
this.logger.log(`Incoming request: ${request.method} ${request.url}`);
return next
.handle()
.pipe(
tap(() => {
this.logger.log(`Request completed in ${Date.now() - now}ms`);
}),
);
}
}
2. Register the Interceptor Globally in AppModule
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from './interceptors/logging.interceptor';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}
Now, the LoggingInterceptor
will be executed for every request that comes into your application.
Applying Interceptors Locally
Applying interceptors locally means they are only executed for specific controllers or routes. This provides more granular control over which requests are intercepted.
How to apply an interceptor locally:
- Use the
@UseInterceptors()
decorator: You can apply interceptors to specific controllers or routes using the@UseInterceptors()
decorator.
Example:
// src/controllers/example.controller.ts
import { Controller, Get, UseInterceptors, Param } from '@nestjs/common';
import { LoggingInterceptor } from '../interceptors/logging.interceptor';
import { TransformInterceptor } from '../interceptors/transform.interceptor';
@Controller('example')
@UseInterceptors(LoggingInterceptor) // Applied to all routes in this controller
export class ExampleController {
@Get()
getExample(): string {
return 'This is an example endpoint.';
}
@Get(':id')
@UseInterceptors(TransformInterceptor) // Applied only to this route
getExampleById(@Param('id') id: string): string {
return `Example ID: ${id}`;
}
}
In this example, LoggingInterceptor
is applied to all routes within the ExampleController
, while TransformInterceptor
is applied only to the getExampleById
route.
Order of Execution of Interceptors
When multiple interceptors are applied, their order of execution is crucial.
Order:
- Global Interceptors: Applied in the order they are defined in the
AppModule
(or any other module whereAPP_INTERCEPTOR
is used). - Controller-Level Interceptors: Applied in the order they are defined using the
@UseInterceptors()
decorator on the controller. - Route-Level Interceptors: Applied in the order they are defined using the
@UseInterceptors()
decorator on the route.
For example, if you have a global interceptor A
, a controller-level interceptor B
, and a route-level interceptor C
, the execution order will be A -> B -> C
before the request reaches the handler, and C -> B -> A
after the handler returns a result. This follows a nested structure.
Example:
Let's say you have the following setup:
- Global Interceptor:
GlobalInterceptor
- Controller-Level Interceptor:
ControllerInterceptor
- Route-Level Interceptor:
RouteInterceptor
When a request hits a specific route within the controller, the order of execution would be:
GlobalInterceptor
(Before the request reaches the controller)ControllerInterceptor
(Before the request reaches the handler)RouteInterceptor
(Before the request reaches the handler)- Handler (The route handler in the controller executes)
RouteInterceptor
(After the handler returns a result)ControllerInterceptor
(After the handler returns a result)GlobalInterceptor
(After the handler returns a result)
This nested execution order allows you to chain interceptors to perform complex transformations and operations on the request and response.
Important Considerations
- Exception Handling: Interceptors can be used to catch and handle exceptions thrown by route handlers.
- Performance: Be mindful of the performance impact of adding multiple interceptors, especially global ones.
- Dependency Injection: Interceptors can inject dependencies like other services or repositories.