NestJS is recognized for its modular architecture and a plethora of built-in features that help developers create powerful backend applications. Among these features, interceptors stand out as a powerful tool for enhancing the functionality of your APIs. In this guide, we’ll delve deeper into what interceptors are, how they work, and some practical use cases.
Interceptors are a type of provider in NestJS, designed to intercept and transform incoming requests or outgoing responses. They sit between the request and the response cycle, providing a convenient way to execute custom logic, such as logging, transforming data, or managing exceptions.
To understand interceptors better, it's essential to grasp where they fit into the request lifecycle:
Creating an interceptor in NestJS is straightforward. It involves a simple class implementing the NestInterceptor
interface. Here’s a quick example that logs the execution time of a 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(); return next .handle() .pipe( tap(() => console.log(`Request executed in ${Date.now() - now}ms`)), ); } }
@Injectable()
decorator is necessary, allowing Nest's dependency injection system to manage the lifecycle of the interceptor.ExecutionContext
and a CallHandler
. The ExecutionContext
provides details about the current request, such as the context of the handler being invoked.Observable
. This is crucial in order to enable the interceptor to process the response and perform side effects using RxJS operators like tap
.Once you’ve created an interceptor, you might want to apply it globally to affect all controllers in the application. Here’s how to do just that in your main application 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();
Alternatively, if you only want to use an interceptor for specific controllers or routes, you can do so by attaching it directly in the controller:
import { Controller, Get, UseInterceptors } from '@nestjs/common'; import { LoggingInterceptor } from './logging.interceptor'; @Controller('cats') @UseInterceptors(LoggingInterceptor) export class CatsController { @Get() findAll() { // Logic to return all cats return []; } }
Interceptors can serve various purposes in your NestJS application. Here are some common real-world use cases:
You might want to consistently modify the shape of your responses. For instance, wrapping all responses in a standard format:
@Injectable() export class ResponseFormatInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map(data => ({ success: true, data, })), ); } }
Interceptors are great for handling exceptions globally. You could log the error details or return a custom response format using the following interceptor:
@Injectable() export class ExceptionInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( catchError(err => { console.error(err); throw new HttpException('Something went wrong', HttpStatus.INTERNAL_SERVER_ERROR); }), ); } }
One practical application of interceptors is caching responses. Here’s a simple illustration of caching:
@Injectable() export class CachingInterceptor implements NestInterceptor { private cache = new Map(); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const key = this.generateKey(context); if (this.cache.has(key)) { return of(this.cache.get(key)); // Return cached response } return next.handle().pipe( tap(response => this.cache.set(key, response)) // Cache the response ); } private generateKey(context: ExecutionContext): string { // Logic to generate a unique key based on context return context.switchToHttp().getRequest().url; } }
Tracking and identifying cache misses usually enhances performance and optimizes resource usage.
Interceptors in NestJS provide a powerful foundation for adding consistent behavior across your application. Whether it’s logging, transforming data, handling exceptions, or caching responses, they help keep your application clean and maintainable. Understanding and implementing interceptors will greatly enhance your ability to structure robust and efficient backend APIs. As you build out your NestJS projects, leverage the power of interceptors to create cleaner, more modular code!
10/12/2024 | NestJS
10/12/2024 | NestJS
10/12/2024 | NestJS
10/12/2024 | NestJS
10/12/2024 | NestJS
10/12/2024 | NestJS
10/12/2024 | NestJS
10/12/2024 | NestJS