Stay tuned for the *NestJs Journey * series of articles
The interceptor is a class that implements the NestInterceptor interface and is decorated with the @Injectable decorator.
Interceptors are applications based on AOP programming ideas. The following are common features:
- Perform additional logic before or after method execution, which is generally not part of the business
- Conversion function execution result
- An exception thrown when the conversion function executes
- Extend the basic behavior of functions
- Completely rewriting the behavior of a function in a particular scenario (such as a cache interceptor that returns as soon as a cache is available, without executing the real business logic, i.e. the business logic handling the behavior of the function has been overridden)
Interceptor interface
Each interceptor needs to implement the ** Intercept ()** method of the NestInterceptor interface, which takes two arguments. The method prototype is as follows:
function intercept(context: ExecutionContext, next: CallHandler) :Observable<any>
Copy the code
- ExecutionContext ExecutionContext, same as the ExecutionContext in the route guard in NestJs learning tour (7)
- CallHandler Specifies the route handler function
CallHandler
This interface is an abstraction of the routing handler function and is defined as follows:
export interface CallHandler<T = any> {
handle(): Observable<T>;
}
Copy the code
The return value of handle() is also the return value of the corresponding routing function.
Take getting the user list as an example:
// user.controller.ts
@Controller('user')
export class UserController {
@Get()
list() {
return[]; }}Copy the code
When calling /user/list, the route handler returns **[]**. In the case of an interceptor, calling the Handle () method of the CallHandler interface results in an Observable<[]>(RxJs wrapper object).
So, if the next.handle() method is called in the interceptor, the corresponding route handler is executed; if not, it is not executed.
A request link logging interceptor
With the rise of microservices, the original single project is divided into a number of relatively small sub-modules, which can be independently developed, deployed and run, greatly improving the efficiency of development and execution, but also brings more problems. One problem that is often encountered is that interface invocation errors are difficult to find logs.
If hard-coding such link call logs in business logic is not desirable, it is a serious violation of the principle of single responsibility, which is a bad practice in microservice development and can make microservices bloated, the logic can be implemented with interceptors.
// app.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Request } from 'express';
import { format } from 'util';
@Injectable(a)export class AppInterceptor implements NestInterceptor {
private readonly logger = new Logger(); // instantiate the logger
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now(); // Request start time
return next.handle().pipe(tap((response) = > {
// Call handle() to get the RxJs response object. Use tap to get the return value of the routing function
const host = context.switchToHttp();
const request = host.getRequest<Request>();
// Prints request method, request link, processing time and response data
this.logger.log(format(
'%s %s %dms %s',
request.method,
request.url,
Date.now() - start,
JSON.stringify(response), )); })); }}Copy the code
// user.controller.ts
@UseInterceptors(AppInterceptor)
export class UserController {
@Get()
list() {
return[]; }}Copy the code
The console wants to output when accessing /user
[Nest] 96310 - 09/10/2019, 2:44 PM GET /user 1ms []
Copy the code
Interceptor scope
Interceptors can be bound in the following scopes:
- Global interceptor
- Controller interceptor
- Route method interceptor
Global interceptor
Use the following code in main.ts:
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new AppInterceptor());
Copy the code
Controller interceptor
All routing methods of the controller take effect:
@Controller('user')
@UseInterceptors(AppInterceptor)
export class UserController {
}
Copy the code
Route method interceptor
Intercepts only the currently decorated routing method:
@Controller('user')
export class UserController {
@UseInterceptors(AppInterceptor)
@Get()
list() {
return[]; }}Copy the code
Response processing
The return value of the Handle () of the CallHandler interface is actually an RxJs Observable, which can be operated on using the RxJs operator. For example, an API interface returns the following data structure: if the response is normal, the response body is the data without wrapping structure:
{
"id":1."name":"xialei"
}
Copy the code
The new requirement is to wrap the previous pure data response as a data attribute, structured as follows:
{
"data": {
"id": 1."name":"xialei"}}Copy the code
While some of you may already be reviewing the number of responding interfaces and evaluating the time required to develop, an interceptor using NestJs can do this in less than a joss stick.
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable(a)export class AppInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().
pipe(map(data= > ({ data }))); The map operator is similar to array.prototype. map}}Copy the code
After the interceptor is applied, the response data is wrapped in a layer of data attributes.
Anomaly map
Another interesting example is using RxJs catchError to override an exception thrown by a route handler function.
import {
Injectable,
NestInterceptor,
ExecutionContext,
BadGatewayException,
CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable(a)export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
catchError(err= > throwError(new BadGatewayException())) // catchError is used to catch exceptions); }}Copy the code
Rewrite the routing function logic
It was mentioned at the beginning of the article that interceptors can override the routing handler logic. Here is an example of a cache interceptor
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
@Injectable(a)export class CacheInterceptor implements NestInterceptor {
constructor(private readonly cacheService: CacheService) {}
async intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const host = context.switchToHttp();
const request = host.getRequest();
if(request.method ! = ='GET') {
// A non-get request is allowed
return next.handle();
}
const cachedData = await this.cacheService.get(request.url);
if(cachedData) {
// Match the cache
return of(cachedData);
}
return next.handle().pipe(tap(response) => {
// The response data is written to the cache
this.cacheService.set(request.method, response); }); }}Copy the code
At the end
This article is the last in a series of NestJs basics that will be updated for specific modules, such as databases, uploads, authentication, and more.
Due to the direct release of group QR code leads to a very low threshold to add group, recently there are miscellaneous people scan code into the group to send advertising/malicious information, serious harassment of group members, two-dimensional code into the group channel has been closed. Partners in need can pay attention to the public account to obtain group qualification.