In NestJS, interceptors are used to modify the response returned by a route handler. They allow you to execute additional logic before the final response is sent back to the client. Interceptors are ideal for tasks such as transforming response data, logging, caching, or adding custom headers to the response.
In this module, we will explore how to create and use interceptors in NestJS to transform and log responses.
Table of Contents
- Introduction
- What are Interceptors in NestJS?
- Creating Custom Interceptors
- Using Interceptors to Transform Responses
- Logging Responses with Interceptors
- Applying Interceptors Globally and Locally
- Best Practices for Interceptors
- Conclusion
Introduction
Interceptors are a key feature in NestJS for handling the request-response cycle. While middleware handles requests before reaching the route handler, interceptors operate after the route handler has processed the request but before the response is sent to the client. This allows you to perform various operations, such as transforming or logging the response.
In this module, we’ll dive into creating and using interceptors to transform and log responses. We’ll also cover how to apply interceptors globally and locally in your NestJS application.
What are Interceptors in NestJS?
In NestJS, interceptors are classes that implement the NestInterceptor
interface. They allow you to manipulate or modify the incoming response before it’s sent to the client. Interceptors are especially useful for:
- Transforming the response data (e.g., modifying the structure or format).
- Logging the response data for debugging or auditing purposes.
- Enhancing performance (e.g., caching the response).
- Error handling in a centralized manner.
Interceptors are executed after the route handler has processed the request, but before the response is returned. You can use interceptors to modify the response body, add headers, or perform any other transformations.
Characteristics of Interceptors:
- Interceptors can modify the request, response, or both.
- They can be used for transformation, logging, caching, and error handling.
- Interceptors can be applied globally or locally.
- They are executed in the order they are defined.
Creating Custom Interceptors
To create a custom interceptor, you need to implement the NestInterceptor
interface and define the intercept()
method. The intercept()
method takes two parameters:
- context: The execution context, which provides access to the request and response.
- next: The function that passes control to the next interceptor or handler.
Here’s an example of a basic interceptor that logs the response time for each request:
Step 1: Create a Logging Interceptor
typescriptCopyEditimport { 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();
return next
.handle()
.pipe(
tap(() => console.log(`Response time: ${Date.now() - now}ms`)),
);
}
}
In this example:
- The
LoggingInterceptor
logs the time taken for each request to be processed. - We use the
tap
operator from RxJS to log the response time after the request is processed.
Step 2: Register the Interceptor
To use this interceptor, you must register it in a module. You can apply the interceptor globally or locally.
Globally:
In main.ts
, you can apply the interceptor globally to all routes:
typescriptCopyEditimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './logging.interceptor';
import { Reflector } from '@nestjs/core';
import { APP_INTERCEPTOR } from '@nestjs/core';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();
Locally:
You can apply the interceptor to specific controllers or routes using the @UseInterceptors()
decorator:
typescriptCopyEditimport { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';
@Controller('users')
export class UserController {
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {
return ['User1', 'User2', 'User3'];
}
}
Using Interceptors to Transform Responses
One of the most common uses of interceptors is to transform the response before it reaches the client. You can modify the response data structure or apply any transformation logic you need.
Here’s an example of a transformation interceptor that adds a timestamp
property to the response:
Step 1: Create a Transformation Interceptor
typescriptCopyEditimport { 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) => ({
data,
timestamp: new Date().toISOString(),
})),
);
}
}
In this example:
- The
TransformInterceptor
modifies the response by wrapping it in an object that includes the original data and a timestamp.
Step 2: Apply the Transformation Interceptor
You can apply the TransformInterceptor
globally or locally as demonstrated earlier. Here’s how to apply it locally in a controller method:
typescriptCopyEdit@Controller('posts')
export class PostController {
@Get()
@UseInterceptors(TransformInterceptor)
findAll() {
return { title: 'NestJS Interceptors', content: 'Learn how to use interceptors' };
}
}
This will ensure that every response from the findAll
method is transformed to include the timestamp
.
Logging Responses with Interceptors
Logging is a common use case for interceptors. You can log details such as the response body, status code, or any other relevant information before the response is sent to the client.
Here’s an example of a logging interceptor that logs the response data:
Step 1: Create a Logging Interceptor
typescriptCopyEditimport { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class ResponseLoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap((response) => {
console.log('Response Data:', response);
}),
);
}
}
In this example:
- The
ResponseLoggingInterceptor
logs the response data before it is sent to the client.
Step 2: Apply the Logging Interceptor
typescriptCopyEdit@Controller('posts')
export class PostController {
@Get()
@UseInterceptors(ResponseLoggingInterceptor)
findAll() {
return { title: 'NestJS Interceptors', content: 'Learn how to use interceptors' };
}
}
This will log the response data every time the findAll
method is called.
Applying Interceptors Globally and Locally
As with middleware, you can apply interceptors globally or locally in your NestJS application.
Globally:
To apply an interceptor globally, use the app.useGlobalInterceptors()
method in main.ts
:
typescriptCopyEditimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TransformInterceptor } from './transform.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new TransformInterceptor());
await app.listen(3000);
}
bootstrap();
Locally:
To apply an interceptor locally to specific controllers or routes, use the @UseInterceptors()
decorator:
typescriptCopyEdit@Controller('posts')
export class PostController {
@Get()
@UseInterceptors(TransformInterceptor)
findAll() {
return { title: 'NestJS Interceptors', content: 'Learn how to use interceptors' };
}
}
Best Practices for Interceptors
- Keep Interceptors Focused: An interceptor should be focused on a single concern, such as logging, transforming, or caching.
- Use for Cross-Cutting Concerns: Interceptors are perfect for handling tasks that apply to multiple routes, such as logging, transforming data, or managing headers.
- Avoid Complex Logic in Interceptors: Interceptors should not contain business logic. They should be lightweight and focused on modifying the request/response or performing non-business tasks.
- Chain Multiple Interceptors: You can chain multiple interceptors to perform several tasks sequentially. Ensure the order of execution is correct.
Conclusion
Interceptors in NestJS offer a powerful way to transform and log responses, making them ideal for tasks like data transformation, logging, or even error handling. By using interceptors effectively, you can maintain cleaner, more maintainable code and handle cross-cutting concerns efficiently.
Now that you understand how to create and use interceptors for transforming and logging responses, you can implement them in your NestJS applications to streamline your response handling and ensure a more robust architecture.