A Transform Interceptor
Wrap every response in a consistent envelope using an interceptor and RxJS map.
return next.handle().pipe(map(data => ({ data })))APIs often return a consistent envelope, such as { data: ... }. A transform interceptor reshapes every handler's return value into that structure.
Using RxJS map
Call next.handle() to get the response stream, then use map to wrap the value. Because it is global, every endpoint returns the same shape without extra code in handlers.
Example
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
interface Response<T> { data: T; }
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(ctx: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map((data) => ({ data })));
}
}When to use it
- A team wraps every API response in { success: true, data: <payload> } using a transform interceptor so front-end clients always receive the same envelope.
- A developer uses the transform interceptor to strip undefined values from responses before they reach the client.
- An engineer pairs the transform interceptor with ClassSerializerInterceptor so responses are both enveloped and have sensitive fields excluded.
More examples
Response envelope interceptor
Wraps every response payload in a { data } envelope using a generic RxJS map operator, providing a consistent API response structure.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, map } from 'rxjs';
export interface Response<T> { data: T; }
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>> {
intercept(_ctx: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({ data })));
}
}Enriched envelope with metadata
Adds success flag, timestamp, and request path to the envelope so clients get structured metadata alongside every response payload.
intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(data => ({
data,
success: true,
timestamp: new Date().toISOString(),
path: ctx.switchToHttp().getRequest().url,
})),
);
}Global transform via APP_INTERCEPTOR
Registers TransformInterceptor globally using the APP_INTERCEPTOR token, applying the envelope to every handler without decorating each controller.
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TransformInterceptor } from './transform.interceptor';
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
],
})
export class AppModule {}
Discussion