In a modern API-driven application, maintaining a consistent response format is crucial for better client-side handling and debugging. NestJS provides a powerful mechanism called Interceptors, which allows us to modify and enhance response structures globally or at the controller level.
What is an Interceptor in NestJS?
Interceptors in NestJS sit between the request and response cycle, allowing us to:
- Modify the response before sending it to the client.
- Handle logging, transformation, or caching.
- Implement custom logic like timing requests or modifying headers.
The @Injectable() decorator marks an interceptor as a service that can be used globally or within specific controllers.
Creating a Global Response Interceptor
Let's analyze the following ResponseInterceptor implementation:
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
export interface ApiResponse<T> {
message?: string;
meta?: {
total?: number;
page?: number;
limit?: number;
};
data: T;
}
@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<ApiResponse<T>> {
intercept(
context: ExecutionContext,
next: CallHandler<ApiResponse<T>>,
): Observable<{ success: true; data: T; message: string }> {
return next.handle().pipe(
map((data) => {
return {
success: true,
message: data?.message ?? 'Request successful',
meta: data?.meta,
data: data?.data ?? (data as T),
};
}),
);
}
}
Applying the ResponseInterceptor Globally
To apply this interceptor to all responses, register it in the main.ts file:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ResponseInterceptor } from './interceptors/response.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Apply globally
app.useGlobalInterceptors(new ResponseInterceptor());
await app.listen(3000);
}
bootstrap();
Using the Interceptor in Specific Controllers
If you want to use the ResponseInterceptor only for certain controllers instead of globally, apply it at the controller level:
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { ResponseInterceptor } from './interceptors/response.interceptor';
@Controller('users')
@UseInterceptors(ResponseInterceptor)
export class UserController {
@Get()
getUsers() {
return { data: [{ id: 1, name: 'John Doe' }], message: 'Users fetched' };
}
}
Benefits of Using a Response Interceptor
✅ Consistent API responses across all endpoints.
✅ Reduces boilerplate – No need to manually structure responses in every controller.
✅ Improves maintainability – Easy to update response format centrally.
✅ Enhances API documentation by enforcing a structured format.
Regards,
N I Rimon
Top comments (0)