Introduction
As a backend developer consistent responses are important to make your client can easily interact with API you’ve made. Without a standard structure, responses felt chaotic, make it harder to debug and for others to work with the data. To handle this, I explored ways to make my responses clean and predictable. Along the way, I learned about some helpful technique it called Interceptors that made a big difference. I’ll share what I learned about keeping API responses consistent in NestJS, make it easier for everyone to work with my API.
Standardize your API
Before you create an API, it’s essential to establish a clear standardization plan. This helps ensure consistency, readability, and ease of use for developers interacting with it. Most of modern APIs today use the RESTful approach, which is stand for (Representational State Transfer). RESTful APIs follow a set of architectural principles that make them scalable, flexible and easy to understand. We will talk about RESTful API later.
What is NestJS Interceptors?
In NestJS Interceptors are like helper function that sit in the middle of request and response of process in you application, it sounds like same as middleware but middleware is called only before the request reaches to the controller and also middleware can only handle the request.
Let’s standardize our API
I want the the API response if it return success it should be like this:
{
"statusCode": 200,
"message": "Data get successfully",
"data": [...],
}
But if it failed or error it should be like this:
{
"statusCode": 500,
"message": "Internal Server Error",
"error": "InternalServerErrorException",
"timestamp": 1730691956164,
"data": {}
}
note: if you want to catch everything you can using Global Exception Filter, that I will talk about it later.
Defining an Interceptors
First of all you can follow this steps:
- Create new file for handling the response interceptors
I named it response.interceptor.ts
it located on src/client/response.interceptor.ts
- Then Create the response interceptor class that implements to NestInterceptor
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observalble<any> {
return next.handle().pipe();
}
}
The intercept method takes two arguments. The first is ExecutionContext
, which provides information about the current request and what’s happening in the request process. And the second is CallHandler
which includes a handle()
method that you can call to run the function that actually processes the request. Intercept allows you to check details of the request and then call the handler when needed.
- Don’t forget to import the required module
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
- Then mapping the response
return next.handle().pipe(
map((data) => {
// mapping your response here
})
)
The data
value is returned by your route handler which means return value of your controller.
- Register your interceptors
The interceptor required to register inside your application by registering as provider in app.module.ts
.
- Now you can manipulate the response
If you want to change the message each of HTTP Method you implementing like this :
....
const response = context.switchToHttp().getResponse();
const statusCode = response.statusCode;
const httpMethod: unknown = context.switchToHttp().getRequest().method;
let message: unknown = '';
return next.handle().pipe(
map((data) => {
switch (httpMethod) {
case 'GET':
message = 'Data get successfully';
break;
case 'POST':
message = 'Data created successfully';
break;
case 'PATCH':
case 'PUT':
message = 'Data updated successfully';
break;
case 'DELETE':
message = 'Data deleted successfully';
break;
default:
message = 'Successfully';
break;
}
return {
statusCode,
message,
data
}
})
)
....
from that approach it return the response like this :
{
"statusCode": 200,
"message": "Data get successfully",
"data": "Hello World!"
}
now it same as we expected before. But take a look closer inside the switch case :
switch (httpMethod) {
case 'GET':
message = 'Data get successfully';
break;
case 'POST':
message = 'Data created successfully';
break;
case 'PATCH':
case 'PUT':
message = 'Data updated successfully';
break;
case 'DELETE':
message = 'Data deleted successfully';
break;
default:
message = 'Successfully';
break;
}
all message it successfully what happen if the response returned an error or failed? it would be like this?
{
"statusCode": 500,
"message": "Data get successfully",
"data": null
}
No, it will not like that, by default when have exception it will handle by HttpException
which is build-in exception filter provided by NestJS. So the response will be like this :
{
"message": "Internal Server Error",
"statusCode": 500
}
So how do I manipulate the exception response?
You can add catchError()
method inside the pipe()
.
The catchError()
method is to catches errors on the observable.
catchError((err) => {
const statusCode = err instanceof HttpException ? err.getStatus() : 500;
const errorResponse = {
statusCode,
message: err.message || new InternalServerErrorException(),
error: err.name || 'Error',
timestamp: Date.now(),
data: {},
};
return throwError(() => new HttpException(errorResponse, statusCode));
}),
the code above it to change the response when it got an exception or got an error. Now the response will be something like this :
{
"statusCode": 500,
"message": "Internal Server Error",
"error": "InternalServerErrorException",
"timestamp": 1730706189471,
"data": {}
}
Thats it.
Here the whole code of ResponseInterceptor
class that we’ve made :
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
HttpException,
InternalServerErrorException,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const response = context.switchToHttp().getResponse();
const statusCode = response.statusCode;
const httpMethod: unknown = context.switchToHttp().getRequest().method;
let message: unknown = '';
return next.handle().pipe(
map((data) => {
switch (httpMethod) {
case 'GET':
message = 'Data get successfully';
break;
case 'POST':
message = 'Data created successfully';
break;
case 'PATCH':
case 'PUT':
message = 'Data updated successfully';
break;
case 'DELETE':
message = 'Data deleted successfully';
break;
default:
message = 'Success';
break;
}
return {
statusCode,
message,
data,
};
}),
catchError((err) => {
const statusCode = err instanceof HttpException ? err.getStatus() : 500;
const errorResponse = {
statusCode,
message: err.message || new InternalServerErrorException(),
error: err.name || 'Error',
timestamp: Date.now(),
data: {},
};
return throwError(() => new HttpException(errorResponse, statusCode));
}),
);
}
}
The interceptors is a useful helper it can manipulate your response, logging or monitoring, transforming requests and a lot more. That I will talk about it later.
Top comments (0)