DEV Community

Cover image for Enhancing API Responses with a Global Response Interceptor in NestJS
Nurul Islam Rimon
Nurul Islam Rimon

Posted on

Enhancing API Responses with a Global Response Interceptor in NestJS

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),
        };
      }),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

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' };
  }
}

Enter fullscreen mode Exit fullscreen mode

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

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay