concept

If the previous article introduced Nest modularity as a static way to understand Nest, the next part of the Nest dynamic process in the runtime. While earlier articles briefly outlined a few key concepts in Nest’s handling of requests and processes, in the next few articles, let’s take a closer look at Nest’s processing.

Express middleware

Middleware is a powerful feature of Express and provides rich extensibility. Request and Response objects can be accessed and accessed by any middleware, using the next() function to hand control to the next middleware. Nest’s middleware is basically equivalent to Express (because it inherits Express). For Express middleware, its official documentation defines it as:

  • Arbitrary code can be executed
  • Modify the Request and Response objects
  • Terminates the request response process
  • Call the next middleware method through the next() function
  • If it is not the last link, next() must be executed to pass control

Tip: All middleware is executed before the associated path is mapped to the controller.

Express middleware can be broadly divided into five types:

  1. Global middleware: Whatever is executed is executed;
  2. Routing middleware: middleware subordinate to a route;
  3. Error-handling middleware: specialized in handling exceptions. The first parameter of the middleware entry is an error object.
  4. Built-in middleware: functionality integrated within Express;
  5. Third-party middleware: middleware developed by a third party, such as Cookie conversion, etc.

Nest middleware

Nest’s middleware basically inherits ideas from Express. The process is shown as follows:

Because Nest abstracts the Router from Express into a Controller, the process of registering middleware is placed in the Module. As a result, Nest has a very clear distinction between middleware and Controller(the equivalent of Router in Express) applications compared to Express.

Creating a middleware

Create middleware with command line shortcuts:

nest g mi Logger

Class (morphological) middleware

By default, Nest creates a middleware object as a class.

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable(a)export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => void) {
    console.log(
      `The browser's accepted header is : ${req.headers['accept-language']}`,); next(); }}Copy the code

Functional (morphological) middleware

In addition to class-declared middleware, Nest also supports defining middleware functionally, as described in the following code.

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(
      `The browser's accepted language is : ${req.headers['accept-language']}`,); next(); };Copy the code

Middleware Registration

Finally, the middleware can be registered in any module to complete the whole process of writing and registering middleware.

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('/'); }}Copy the code
curl localhost:3000 -H "accept-language: Chinese"
The browser's accepted language is : Chinese
Hello world!
Copy the code

In-depth understanding of middleware

NestMiddleware interface

At Nest, middleware also needs to be managed by an IoC container, which means that classes and functions are handled with an @Injectable() decorator, just like other providers. If middleware is a class, then you need to implement the NestMiddleware interface, which in the Nest source code has only one use function to define.

export interface NestMiddleware<TRequest = any, TResponse = any> {
  use(req: TRequest, res: TResponse, next: () = > void) :any;
}
Copy the code

Where, REq and RES correspond to request and response objects respectively. You can see that the category is defined as any. If you want auto-completion of strongly typed code, you need to manually respecify the type (as in the example above, specifying the request and response objects for Express).

next()

The next method is to pass the current control to the next person. Note: When next is executed, no arguments are allowed. The only arguments are used if and only if an exception occurs. For example, change the code to:

export class ReqMiddleware implements NestMiddleware {
  use(req: ERequest, res: any, next: (T? :any) = >void) {
    if (req.query.key) {
      console.log(req.query.key);
      next();
    } else next('Some error occurred'); }}Copy the code
[Nest] 13699   - 01/27/2021, 4:10:53 PM   [ExceptionsHandler] Some error occurred +3796ms
Copy the code

How do you pass parameters between multiple middleware? As suggested by the official Express documentation, you can use res objects. Such as:

export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => void) {
    req.query.key = req.headers['accept-language']; next(); }}Copy the code

You can also use the next() method in controller functions as mentioned earlier in the controller section. As mentioned earlier in this article with Express, using Nest () in a controller is equivalent to treating the controller as a routing middleware.

Handles multiple middleware and complex routing

By default, middleware files created from the command line are placed in the project’s SRC directory. But the effect of a middleware program is the same regardless of which module it is registered in. And use the forRoutes and exclude methods to set the request path that applies to it.

Registered modules need to implement the NestModule interface; The interface has only one method, override the configure function; The function has only one argument, MiddlewareConsumer, of the interface.

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('/');
    consumer.apply(ReqMiddleware).forRoutes('/user'); }}Copy the code

Chain registration (streaming interface)

According to the source information, MiddlewareConsumer only has one apply method, which returns MiddlewareConfigProxy interface. The interface contains two methods, exclude and forRouters, which represent to exclude and include some routes respectively. Exclude returns MiddlewareConfigProxy, while forRouters returns MiddlewareConsumer. This creates a chain call. The above code can be changed to:

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('/')
      .apply(ReqMiddleware)
      .forRoutes('/user'); }}Copy the code

Multiple middleware can be registered with a module, and if they have the same scope, the code can also be changed to:

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware,ReqMiddleware)
      .forRoutes('/'); }}Copy the code

For the parameter type RouteInfo, you can pinpoint specific methods in Controller.

export interface RouteInfo {
  path: string;
  method: RequestMethod;
}
Copy the code

The registration process above can also be changed to:

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({path:'/'.method:RequestMethod.GET})
      .apply(ReqMiddleware)
      .forRoutes({path:'/user'.method:RequestMethod.GET}); }}Copy the code

Wildcards and even regular expressions can be supported if they are strings (additional components are required if you want to use regular expressions)