Let’s learn how to use interceptors in NestJS, its integration points, and the difference between interceptors and middleware.
Introduction
Interceptors follow the core concept of Aspect Oriented Programming (AOP). It aims to increase modularity and makes it possible for the separation of concerns. Interceptors are used to bind logic before a request is executed and after the route handler returns the response. Interceptors are used to transform requests and responses.
Interceptor classes implement the NestInterceptor interface. It has access to the intercept()
method, it takes the argument ExecutionContext
and the CallHandler
. The ExecutionContext
has access to the request, response, and next object. The CallHandler
is used to invoke the route handler method. With the CallHandler you can also access the response data by taping into it.
Let’s look at how the interceptors work in NestJS, from the image below.
As we can see unlike middleware and guards the working point of interceptors in NestJS is after the route has handled the request and sent the response back to the client.
Prerequisites
Interceptors in NestJS
We have looked at, how Interceptors in NestJS generally function, now let’s look at how it is made and how it is applied globally and in a single route.
Firstly create a nest project.
## Generating a nest project
$ nest new nest-interceptor
## for package manager select according to your choice
## article uses: npm
After your NestJS project is created, open it with your favorite code editor. Now that the project is set up let’s move on to the actual part of creating the interceptor. the guard can simply be created by using the nest CLI.
$ nest g interceptor app --no-spec
The --no-spec
option makes sure that the test classes are not generated.
Now, let’s edit the interceptor and make it so that the API response body is logged along with the user agent. Let us look at that in actual code, and learn about interceptors in NestJS.
//* app.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class AppInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const userAgent = context.switchToHttp().getRequest().headers['user-agent'];
return next.handle().pipe(
tap(data => {
console.log(`----------Request start: ${userAgent}----------`)
console.log(data)
console.log(`----------Request end: ${userAgent}----------`)
})
);
}
}
The above code will log the response sent back to the client, along with the user-agent
of the client.
Now, let’s bind the interceptor in a single route handler which is present in the controllers, which is the /
base route.
//* app.controller.ts
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { AppInterceptor } from './app.interceptor';
@Controller()
export class AppController {
@Get()
@UseInterceptors(AppInterceptor)
getHello(): string {
return 'Hello !'
}
@Get('/no-interceptor')
getBye(): string {
return 'Bye !';
}
}
Route with interceptor
Route without interceptor
Now, that we have invoked the route containing the logging interceptor, the response
body is logged, and the user-agent of the client is also logged. When Invoking a route that does not contain an interceptor is invoked, the logging action does not happen.
Let us look at applying Interceptors in NestJS globally. For applying Interceptors globally we need to apply the guard in the main.ts
file, or the starting point of the application. We need to add the code app.useGlobalInterceptors()
there to add the interceptor globally, let’s look at the code below.
//* main.ts
import { NestFactory } from '@nestjs/core';
import { AppInterceptor } from './app.interceptor';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new AppInterceptor())
await app.listen(3000);
}
bootstrap();
Now let’s test out the /no-interceptor
route which was not protected by the guard, previously now it is protected by the global guard.
Route without interceptor after global implementation
We can see that, even if the interceptor is not included, the interceptor is still invoked because it is added globally by using the app.useGlobalInterceptors()
in the main.ts
file.
Difference Between Middleware and Interceptor
Middlewares and Interceptors act similarly, meaning they have access to the request and the response object, and manipulate it. The difference is that middleware can only act on the request that is sent by the client before it hits the route handler. Whereas the interceptor can act before the request is handled by the route handler and also after the response leaves the route handler. The middleware acts on the request part, while the interceptor works on both the request and response part.
Conclusion
In this article, we learned about using interceptors in NestJS. We looked upon a practical example of it, by implementing a logger using an interceptor, and also looked at the difference between middleware and interceptors in NestJS. Like interceptors, there are also guards to learn more about NestJs guards look into our article How To Use Guards in NestJS.