Skip to main content
Practice Problems

What are interceptors in NestJS?

Interceptors in NestJS

Interceptors wrap the execution of a route handler and can:

  • Execute logic before and/or after a handler
  • Transform the result returned by a handler
  • Transform exceptions
  • Override or extend basic handler behavior (e.g., cache)
  • Measure execution time

They implement the NestInterceptor interface and use RxJS Observables.


Interceptor Structure

typescript
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() // calls the route handler .pipe( tap(() => console.log(`Elapsed: ${Date.now() - now}ms`)) ); } }

Common Interceptor Patterns

1. Response Transform (Wrap in envelope)

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

2. Caching

typescript
@Injectable() export class CacheInterceptor implements NestInterceptor { constructor(private cacheService: CacheService) {} async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { const key = context.switchToHttp().getRequest().url; const cached = await this.cacheService.get(key); if (cached) return of(cached); // return from cache, skip handler return next.handle().pipe( tap(result => this.cacheService.set(key, result, 60)) ); } }

3. Execution Timing

typescript
@Injectable() export class TimingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); const req = context.switchToHttp().getRequest(); return next.handle().pipe( tap(() => { const duration = Date.now() - start; console.log(`${req.method} ${req.url}${duration}ms`); }) ); } }

4. Exception Mapping

typescript
import { catchError } from 'rxjs/operators'; import { throwError } from 'rxjs'; @Injectable() export class ErrorsInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( catchError(err => throwError(() => new BadGatewayException('External service error')) ) ); } }

Applying Interceptors

typescript
// Method-level @Get() @UseInterceptors(LoggingInterceptor) findAll() { ... } // Controller-level @Controller('users') @UseInterceptors(TransformInterceptor) export class UsersController {} // Global (in module, supports DI) @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: TransformInterceptor }, { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }, ], })

NestJS Request Lifecycle

Incoming Request Middleware Guards (can reject) Interceptors (before) Pipes (transform/validate) Route Handler Interceptors (after) Exception Filters (if error) Outgoing Response

Summary

Interceptors are powerful tools for cross-cutting concerns — wrapping all responses in a consistent envelope, logging, caching, timing, and error mapping. Use RxJS map, tap, and catchError operators to react to handler execution and transform results.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems