NestJs Learning Tour-Interceptor

Keywords: node.js Programming JSON Attribute less

Welcome to NestJs Travel Series

Interceptor is a class that implements the NestInterceptor interface and is decorated by the @Injectable decorator.

Interceptor is an application based on AOP programming idea. The following are commonly used functions:

  • Execute additional logic before or after method execution, which is generally not part of the business
  • Conversion function execution results
  • Exceptions thrown during the execution of conversion functions
  • Basic Behavior of Extended Functions
  • The behavior of completely rewriting functions in specific scenarios (e.g. cache interceptors, which return directly once available caches do not execute real business logic, that is, the behavior of business logic processing functions has been rewritten)

Interceptor interface

Each interceptor needs to implement the intercept() method of the NestInterceptor interface, which receives two parameters. The prototype of the method is as follows:

function intercept(context: ExecutionContext, next: CallHandler): Observable<any>

CallHandler

The interface is an abstraction of the routing function. The interface is defined as follows:

export interface CallHandler<T = any> {
    handle(): Observable<T>;
}

The return value of handle() function is the return value of the corresponding routing function.

Take getting a list of users as an example:

// user.controller.ts
@Controller('user')
export class UserController {
  @Get()
  list() {
    return [];
  }
}

When accessing / user/list, the routing function returns [], and if the interceptor is applied, the handle() method of the CallHandler interface is also called to get Observable <[]> (RxJs wrapper object).

So, if the next.handle() method is called in the interceptor, the corresponding routing function will be executed, and if it is not called, it will not be executed.

A Request Link Log Interceptor

With the rise of micro-services, the original single project has been divided into several smaller sub-modules. These sub-modules can be independently developed, deployed and run independently, which greatly improves the efficiency of development and execution. However, there are many problems. One common problem is that interface calls are not easy to find logs.

If hard-coding this link call log in business logic is very undesirable, it seriously violates the principle of single responsibility. This is a very bad behavior in the development of micro-services, which will make micro-services bloated. These logic can be completely realized by 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()
export class AppInterceptor implements NestInterceptor {
  private readonly logger = new Logger(); // Instantiated Logger

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now(); // Request start time

    return next.handle().pipe(tap((response) => {
      // After calling handle(), the RxJs response object is obtained, and the return value of the routing function can be obtained by tap.
      const host = context.switchToHttp();
      const request = host.getRequest<Request>();

      // Print 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),
      ));
    }));
  }
}
// user.controller.ts
@UseInterceptors(AppInterceptor)
export class UserController {
  @Get()
  list() {
    return [];
  }
}

The console wants to output when accessing / user

[Nest] 96310   - 09/10/2019, 2:44 PM   GET /user 1ms []

Interceptor scope

Interceptors can bind in the following scopes:

  • Global Interceptor
  • Controller Interceptor
  • Routing method interceptor

Global Interceptor

Use the following code in main.ts:

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new AppInterceptor());

Controller Interceptor

All routing methods of the controller will take effect:

@Controller('user')
@UseInterceptors(AppInterceptor)
export class UserController {
}

Routing method interceptor

Intercept only the currently decorated routing methods:

@Controller('user')
export class UserController {
  @UseInterceptors(AppInterceptor)
  @Get()
  list() {
    return [];
  }
}

Response processing

The handle() return value of the CallHandler interface is actually an Observable object of RxJs. The object can be manipulated by using the RxJs operator, such as an API interface. The data structure returned before is as follows. If the response body is data, there is no packaging structure.

{
  "id":1,
  "name":"xialei"
}

The new requirement is to wrap the previous pure data response as a data attribute, structured as follows:

{
  "data": {
    "id": 1,
    "name":"xialei"
  }
}

Some small partners may have sorted out the number of response interfaces and assessed the man-hour to prepare for development when they receive this requirement, while using NestJs interceptors, the requirement can be achieved in less than a simmer of time.

import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
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
  }
}

When the above interceptor is applied, the response data is packaged with a layer of data attributes.

Abnormal mapping

Another interesting example is using RxJs catchError to override exceptions thrown by routing handlers.

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  BadGatewayException,
  CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
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
      );
  }
}

Rewrite routing function logic

At the beginning of the article, it is mentioned that interceptors can rewrite routing function logic. Here is an example of a cache interceptor

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';

@Injectable()
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') {  
      // Non-GET Request Release
      return next.handle();
    }
    const cachedData = await this.cacheService.get(request.url);
    if(cachedData) { 
      // Hit cache, release directly
      return of(cachedData);
    }
    return next.handle().pipe(tap(response) => { 
      // Response data is written to the cache, where you can wait for the cache to be written or not.
      this.cacheService.set(request.method, response);
    });
  }
}

Ending

This is the last part of NestJs'basic knowledge, which will be updated for specific modules, such as database, upload, authentication and so on.

Due to the low threshold of adding group due to the direct release of group two-dimensional codes, recently some people such as micro-merchants swept into the group to send advertisements/malicious information, seriously disturbing group members, and the two-dimensional code entry channel has been closed. Partners in need can pay attention to public numbers to qualify for additional groups.

Posted by greenba on Tue, 10 Sep 2019 01:18:11 -0700