Skip to main content

Custom Response Interceptor In Nestjs

· 5 min read
Sivabharathy

In this article, we will explore how to implement a custom response interceptor in a NestJS application to format all API responses in a standardized structure. This is particularly useful for maintaining consistency across APIs, making it easier for front-end developers and other consumers of the API to parse and understand the responses.


What Is an Interceptor in NestJS?

In NestJS, interceptors are a powerful feature that allow you to:

  1. Transform data before sending it to the client.
  2. Manipulate the request/response.
  3. Perform logging, analytics, or caching.
  4. Add custom headers or structure the API response consistently.

Interceptors are executed before and/or after the route handler, giving you the flexibility to transform the incoming request or the outgoing response.


Custom Response Interceptor

Why Use a Response Interceptor?

The primary goal of this interceptor is to:

  • Standardize API responses.
  • Ensure every response contains metadata such as status_code, message, timestamps, etc.
  • Handle successful responses uniformly, reducing repeated response formatting in controllers.

Standardized Response Structure

Here’s the structure we aim to achieve:

{
"message": "Operation completed successfully",
"status": true,
"data": { "key": "value" },
"error": null,
"timestamps": "2024-12-05T10:45:00.000Z",
"status_code": 200,
"path": "/api/v1/example"
}

Custom Response Interceptor Code

Here’s the implementation of the custom response interceptor in NestJS:

Response Interface

The Response interface defines the structure of the response:

export interface Response<T> {
message: string; // Describes the result of the operation
status: boolean; // Indicates success (true) or failure (false)
data: T; // The actual payload returned to the client
error: null; // Reserved for error details (set to null for successful responses)
timestamps: Date; // Timestamp of the response
status_code: number; // HTTP status code
}

Custom Response Interceptor

The ResponseInterceptor intercepts the response and formats it before sending it to the client:

import { CallHandler, ExecutionContext, NestInterceptor } from "@nestjs/common";
import { map, Observable } from "rxjs";

export class ResponseInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<Response<T>> {
// Extract status code and request path
const statusCode = context.switchToHttp().getResponse().statusCode;
const path = context.switchToHttp().getRequest().url;

// Transform the outgoing response
return next.handle().pipe(
map((data) => ({
message: data.message, // Descriptive message for the operation
status: data.success, // Boolean flag indicating operation success
data: data.result, // Payload data
timestamps: new Date(), // Current timestamp
status_code: statusCode, // HTTP status code
path, // Request path
error: null, // No error in success response
}))
);
}
}

How It Works

  1. The intercept method captures the response object.
  2. The context parameter provides details like HTTP status code and request URL.
  3. The next.handle() processes the response, and map transforms the output into the standardized format.

Using the Interceptor Globally

To apply the interceptor globally across all endpoints, modify the main.ts file:

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ResponseInterceptor } from "./response.interceptor";

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Use the custom ResponseInterceptor globally
app.useGlobalInterceptors(new ResponseInterceptor());

await app.listen(3000);
}
bootstrap();

Testing the Response Interceptor

When you define a route in a controller like this:

import { Controller, Get } from "@nestjs/common";

@Controller("example")
export class ExampleController {
@Get()
getExample() {
return {
message: "Operation completed successfully",
success: true,
result: { key: "value" },
};
}
}

The API response will be intercepted and transformed into:

{
"message": "Operation completed successfully",
"status": true,
"data": { "key": "value" },
"error": null,
"timestamps": "2024-12-05T10:45:00.000Z",
"status_code": 200,
"path": "/example"
}

Benefits of Using a Custom Response Interceptor

  1. Consistency:

    • Ensures all responses follow the same format, making APIs predictable and easier to consume.
  2. Code Simplification:

    • Removes the need to manually format responses in every controller.
  3. Error Handling:

    • You can extend this interceptor to handle error responses uniformly.
  4. Enhanced Debugging:

    • Metadata like timestamps and path make debugging API issues easier.

Extending the Interceptor

Handle Error Responses

You can modify the interceptor to check if the response is an error and format it accordingly:

return next.handle().pipe(
map((data) => {
if (data instanceof Error) {
return {
message: data.message,
status: false,
data: null,
error: data.stack,
timestamps: new Date(),
status_code: statusCode,
path,
};
}
return {
message: data.message,
status: data.success,
data: data.result,
timestamps: new Date(),
status_code: statusCode,
path,
error: null,
};
})
);

Best Practices

  1. Versioning:

    • Include API versioning in the response for better maintainability.
  2. Extend Error Handling:

    • Customize error responses with specific error codes and messages.
  3. Logging:

    • Combine this with a logging service to record all responses.
  4. Type Safety:

    • Use TypeScript generics for stronger type checking in your Response interface.

Conclusion

Using a custom response interceptor in NestJS significantly improves the structure and readability of API responses. It promotes consistency, simplifies controller logic, and makes your API more consumer-friendly. By implementing the example above, your APIs will follow a standardized response format, reducing confusion and errors for API consumers.