Interceptors — Concept for manipulating request and response
Let’s take a real-world example of a Water Filter. The use of a water filter is to remove impurities from the water and make it drinkable. In short, it is doing some logic behind to clear the water impurities, so that clean water can be used for drinking.
Does this mean we should use a water filter for non-drinkable water as well? Precisely, not! It completely depends on what we are going to do with filtered water. That is the context of the water. Similarly, interceptors are a mechanism to do some pre-processing or post-processing with your application requests and responses, depending on what you wanted to do with your request or response.
What does mean pre-processing or post-processing?
Suppose you have an application, which has an authentication mechanism. That means whatever requests you received in your application, you verify whether a valid access token is provided, if not you will throw an error with code 403. In simple words, you are pre-processing your requests before calling actual resources.
Now in the same application, when the access token is valid and the resource part is executed, but before sending it, your intention is to write a response to the logger. Basically this is post-processing the request.
So what is the problem? There is no problem at all, in both cases, we can do this job without the Interceptors. But just think logically, for that you need to add the piece of code in every route of request. Here comes the Interceptor design pattern to save your few lines of code and remove redundancy from code.
Where can we use an Interceptor Pattern?
- bind an extra logic before/after the method execution
- transform the result returned from a function
- transform the exception thrown from a function
- extend the basic function behavior
- completely override a function depending on specific conditions (e.g., for caching purposes)
Example
Taking an example of NestJs framework — A progressive Node.js framework for building efficient, reliable, and scalable server-side applications. You can write NodeJs in Typescript and the huge benefits of using Typescript like OOPs, linting etc.
In this code, you can see we are getting *context* which holds request and response objects. Once you got the request and response you can log it or send it to tools like newrelic or similar.
Now let’s see how can use this, we can use it in three ways(as depends on the specific framework)
- For a specific route
- Globally to handle all requests
- For a specific module
let see how to add interceptor for a specific route in location.controller.ts
@UseInterceptors(new LoggerInterceptor())
export class LocationController {}
For globally, NestJs supports useGlobalInterceptors() method in main.ts which is an entry file.
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggerInterceptor());
For a specific module, e.g. in app.module.ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggerInterceptor,
},
],
})
export class AppModule {}
As listed in the use cases above, it can be used in many places. Feel free to use it.
That's all!!!