乐闻世界logo
搜索文章和话题

What is the role of NestJS interceptors and exception filters?

2月17日 22:32

Interceptor Concept

Interceptors are classes decorated with @Injectable() that implement the NestInterceptor interface. Interceptors have a range of useful capabilities:

  1. Bind extra logic before/after method execution
  2. Transform the result returned from a function
  3. Transform the exception thrown from a function
  4. Extend basic function behavior
  5. Completely override a function depending on specific conditions (e.g., for caching purposes)

Basic Interceptor Structure

typescript
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 => ({ data, timestamp: new Date().toISOString() })) ); } }

Using Interceptors

Use Interceptors on Controllers

typescript
@Controller('cats') @UseInterceptors(TransformInterceptor) export class CatsController {}

Use Interceptors on Methods

typescript
@Controller('cats') export class CatsController { @Get() @UseInterceptors(TransformInterceptor) findAll() { return this.catsService.findAll(); } }

Global Interceptors

typescript
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { TransformInterceptor } from './common/interceptors/transform.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: TransformInterceptor, }, ], }) export class AppModule {}

Common Interceptor Use Cases

1. Logging Interceptor

typescript
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 request = context.switchToHttp().getRequest(); const { method, url, body } = request; const now = Date.now(); this.logger.log(`Incoming request: ${method} ${url}`); return next.handle().pipe( tap(() => { const response = context.switchToHttp().getResponse(); const { statusCode } = response; const delay = Date.now() - now; this.logger.log(`Outgoing response: ${statusCode} - ${delay}ms`); }), ); } }

2. Cache Interceptor

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, of } from 'rxjs'; @Injectable() export class CacheInterceptor implements NestInterceptor { private cache = new Map(); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const cacheKey = request.url; if (this.cache.has(cacheKey)) { return of(this.cache.get(cacheKey)); } return next.handle().pipe( tap(response => { this.cache.set(cacheKey, response); }), ); } }

3. Timeout Interceptor

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common'; import { Observable, throwError, TimeoutError } from 'rxjs'; import { catchError, timeout } from 'rxjs/operators'; @Injectable() export class TimeoutInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( timeout(5000), catchError(err => { if (err instanceof TimeoutError) { return throwError(() => new RequestTimeoutException()); } return throwError(() => err); }), ); } }

4. Response Transform Interceptor

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Response<T> { success: boolean; data: T; message: string; timestamp: string; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> { return next.handle().pipe( map(data => ({ success: true, data, message: 'Success', timestamp: new Date().toISOString(), })), ); } }

Exception Filter Concept

Exception filters are classes decorated with @Catch() that are used to catch and handle exceptions thrown in the application. They allow you to have complete control over the exception handling flow, including response format, status codes, etc.

Basic Exception Filter Structure

typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.message, }); } }

Using Exception Filters

Use Exception Filters on Methods

typescript
@Post() @UseFilters(HttpExceptionFilter) create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto); }

Use Exception Filters on Controllers

typescript
@Controller('cats') @UseFilters(HttpExceptionFilter) export class CatsController {}

Global Exception Filters

typescript
import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; @Module({ providers: [ { provide: APP_FILTER, useClass: HttpExceptionFilter, }, ], }) export class AppModule {}

Common Exception Filters

1. Global Exception Filter

typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const message = exception instanceof HttpException ? exception.message : 'Internal server error'; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message, }); } }

2. Custom Exceptions

typescript
import { HttpException, HttpStatus } from '@nestjs/common'; export class BusinessException extends HttpException { constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) { super( { statusCode: status, message, error: 'Business Error', }, status, ); } } // Use custom exception @Get() findAll() { if (someCondition) { throw new BusinessException('Invalid operation'); } return this.catsService.findAll(); }

3. Validation Exception Filter

typescript
import { ExceptionFilter, Catch, ArgumentsHost, BadRequestException } from '@nestjs/common'; import { ValidationError } from 'class-validator'; @Catch(BadRequestException) export class ValidationExceptionFilter implements ExceptionFilter { catch(exception: BadRequestException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); const exceptionResponse = exception.getResponse(); let errors = []; if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) { errors = (exceptionResponse as any).message; } response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: 'Validation failed', errors, }); } }

Execution Order

The execution order of various processors in NestJS is as follows:

  1. Middleware
  2. Guards
  3. Interceptors before
  4. Pipes
  5. Controller method
  6. Interceptors after
  7. Exception filters

Best Practices

Interceptor Best Practices

  1. Single Responsibility: Each interceptor should only handle one functionality
  2. Performance Considerations: Avoid time-consuming operations in interceptors
  3. Error Handling: Handle errors appropriately in interceptors
  4. Test Coverage: Write tests for interceptors
  5. Documentation: Add clear documentation to interceptors

Exception Filter Best Practices

  1. Unified Response Format: Use a unified exception response format
  2. Logging: Log errors in exception filters
  3. Sensitive Information: Avoid exposing sensitive information in responses
  4. Custom Exceptions: Create custom exception classes to represent business errors
  5. Global Filters: Use global exception filters to handle uncaught exceptions

Summary

The NestJS interceptor and exception filter system provides:

  • Powerful request/response handling capabilities
  • Flexible exception handling mechanisms
  • Clear separation of concerns
  • Easy to extend and customize
  • Complete request lifecycle control

Mastering interceptors and exception filters is key to building robust, maintainable NestJS applications. Interceptors handle cross-cutting concerns of requests/responses, and exception filters handle errors uniformly. Together they form the error handling and response transformation foundation of the application.

标签:NestJS