# How to Implement Custom Interceptors for Response Transformation in NestJS

> Learn how to implement custom interceptors for response transformation in NestJS. Modify data before serialization using the NestInterceptor interface and RxJS map operator.

- Repository: [nestjs/nest](https://github.com/nestjs/nest)
- Tags: how-to-guide
- Published: 2026-03-01

---

**NestJS interceptors let you transform responses by implementing the `NestInterceptor` interface, calling `next.handle()` to access the handler's result, and using RxJS operators like `map` to modify the data before serialization.**

The `nestjs/nest` repository provides a robust interceptor pattern that sits between your controller and the client, allowing you to implement custom interceptors for response transformation across your entire API or specific routes. This pattern leverages RxJS observables to manipulate the response stream after the route handler executes but before the final HTTP response is sent.

## Understanding the NestJS Interceptor Pattern

Interceptors in NestJS implement the **`NestInterceptor<T, R>`** interface, which requires a single `intercept` method receiving two arguments: `ExecutionContext` and `CallHandler`. The `ExecutionContext` provides access to the request, response, and handler metadata, while `CallHandler` represents the next step in the pipeline.

When you call **`next.handle()`**, you receive an `Observable` that emits the route handler's return value. Because this runs **after** the handler but **before** serialization, you can wrap data in envelopes, strip sensitive fields, or convert entities to DTOs. The interceptor returns this modified observable (or a Promise), allowing Nest to complete the response cycle.

## Creating a Response Transformation Interceptor

### Basic Envelope Pattern

A common use case is wrapping every successful response in a consistent structure containing metadata like timestamps or status indicators. In [`sample/01-cats-app/src/core/interceptors/transform.interceptor.ts`](https://github.com/nestjs/nest/blob/main/sample/01-cats-app/src/core/interceptors/transform.interceptor.ts), the framework demonstrates this pattern.

```typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface ResponseEnvelope<T> {
  status: 'success';
  data: T;
  timestamp: number;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, ResponseEnvelope<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<ResponseEnvelope<T>> {
    return next.handle().pipe(
      map((data) => ({
        status: 'success',
        data,
        timestamp: Date.now(),
      })),
    );
  }
}

```

### Conditional Field Stripping

You can access the request object via `context.switchToHttp().getRequest()` to conditionally transform responses based on user roles or permissions. This example removes sensitive fields for non-admin users:

```typescript
@Injectable()
export class HideSensitiveDataInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const isAdmin = request.user?.roles?.includes('admin');

    return next.handle().pipe(
      map((result) => {
        if (isAdmin) return result;
        
        const cleanse = (obj: any) => {
          const { password, ssn, ...rest } = obj;
          return rest;
        };
        
        return Array.isArray(result)
          ? result.map(cleanse)
          : cleanse(result);
      }),
    );
  }
}

```

### Global Date Formatting

For APIs requiring consistent date serialization, implement a recursive formatter that traverses the response object:

```typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import * as dayjs from 'dayjs';

@Injectable()
export class DateFormattingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => this.formatDates(data)),
    );
  }

  private formatDates(value: any): any {
    if (value instanceof Date) {
      return dayjs(value).format('YYYY-MM-DDTHH:mm:ssZ');
    }
    if (Array.isArray(value)) {
      return value.map((v) => this.formatDates(v));
    }
    if (value && typeof value === 'object') {
      const result: any = {};
      for (const [k, v] of Object.entries(value)) {
        result[k] = this.formatDates(v);
      }
      return result;
    }
    return value;
  }
}

```

## Applying Interceptors at Different Scopes

### Route and Controller Level

Use the **`@UseInterceptors()`** decorator to bind interceptors to specific controllers or individual methods. According to the NestJS source, this is the most granular level of application.

```typescript
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { TransformInterceptor } from './core/interceptors/transform.interceptor';

@Controller('cats')
@UseInterceptors(TransformInterceptor)
export class CatsController {
  @Get()
  findAll() {
    return [{ id: 1, name: 'Whiskers' }];
  }
}

```

### Global Registration with APP_INTERCEPTOR

To apply an interceptor to every route in your application, register it as a provider using the **`APP_INTERCEPTOR`** token. This is defined in the core interceptors system and ensures the interceptor runs for all incoming requests.

```typescript
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TransformInterceptor } from './core/interceptors/transform.interceptor';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: TransformInterceptor,
    },
  ],
})
export class AppModule {}

```

## Key Source Files in the NestJS Repository

The `nestjs/nest` repository contains several reference implementations that demonstrate production-grade interceptor patterns:

- **[`packages/common/serializer/class-serializer.interceptor.ts`](https://github.com/nestjs/nest/blob/main/packages/common/serializer/class-serializer.interceptor.ts)** — Built-in interceptor using `class-transformer` to serialize class instances, showing how Nest handles automatic response transformation.
- **[`sample/01-cats-app/src/core/interceptors/transform.interceptor.ts`](https://github.com/nestjs/nest/blob/main/sample/01-cats-app/src/core/interceptors/transform.interceptor.ts)** — Minimal example demonstrating the envelope pattern for wrapping handler results.
- **[`sample/01-cats-app/src/core/interceptors/logging.interceptor.ts`](https://github.com/nestjs/nest/blob/main/sample/01-cats-app/src/core/interceptors/logging.interceptor.ts)** — Illustrates using `tap()` operator to inspect the observable stream without modifying data, useful for combining logging with transformation.
- **[`packages/core/interceptors/interceptors-consumer.ts`](https://github.com/nestjs/nest/blob/main/packages/core/interceptors/interceptors-consumer.ts)** — Core runtime logic that executes the interceptor chain and manages the order of execution.
- **[`packages/core/interceptors/interceptors-context-creator.ts`](https://github.com/nestjs/nest/blob/main/packages/core/interceptors/interceptors-context-creator.ts)** — Responsible for instantiating `ExecutionContext` and preparing dependencies for each interceptor invocation.

## Summary

- **Implement `NestInterceptor`** and override the `intercept` method to gain access to the response stream via `next.handle()`.
- **Use RxJS operators** like `map` to transform data; the observable runs after the handler but before Nest serializes the response.
- **Access request context** through `ExecutionContext` to make transformation decisions based on headers, user roles, or query parameters.
- **Apply globally** using `APP_INTERCEPTOR` provider token, or locally using `@UseInterceptors()` decorator.
- **Reference built-in examples** in [`packages/common/serializer/class-serializer.interceptor.ts`](https://github.com/nestjs/nest/blob/main/packages/common/serializer/class-serializer.interceptor.ts) for complex serialization logic.

## Frequently Asked Questions

### What is the difference between middleware and interceptors in NestJS?

Middleware operates before the route handler runs and cannot access the handler's return value, while interceptors wrap the entire execution context and receive the observable stream of the handler's result. According to the NestJS architecture in [`packages/core/interceptors/interceptors-consumer.ts`](https://github.com/nestjs/nest/blob/main/packages/core/interceptors/interceptors-consumer.ts), interceptors are the only construct that can transform the response body after the controller logic executes.

### Can I modify the HTTP status code in a response interceptor?

Yes, but you must access the response object through `context.switchToHttp().getResponse()` and set the status before the observable completes. However, for status code changes, Guards or Exception Filters are often more appropriate; interceptors excel at data transformation rather than protocol-level modifications.

### How do I access the request object inside an interceptor?

Use `context.switchToHttp().getRequest()` within the `intercept` method. The `ExecutionContext` instance provides this through the `switchToHttp()` helper, as implemented in [`packages/core/interceptors/interceptors-context-creator.ts`](https://github.com/nestjs/nest/blob/main/packages/core/interceptors/interceptors-context-creator.ts). This gives you access to headers, user objects, and query parameters for conditional transformation logic.

### Are interceptors executed in a specific order?

Yes, interceptors execute in the order they are registered. When multiple interceptors are bound, Nest chains them sequentially, with each interceptor's `next.handle()` calling the next in the chain. The execution follows the pattern shown in [`packages/core/interceptors/interceptors-consumer.ts`](https://github.com/nestjs/nest/blob/main/packages/core/interceptors/interceptors-consumer.ts), where the observable flows through each interceptor's RxJS operators before reaching the final serialization step.