background

Nest (NestJS) is a framework for building high-performance, scalable Node.js server applications. It uses progressive JavaScript, has full Typescript built-in support (but developers can program in pure JavaScript), and combines OOP (object-oriented programming), FP (functional programming), and FRP (responsive programming) principles.

Controller

In traditional Node.js server applications, controller is used to handle client requests corresponding to route. Each controller can have multiple routes, and different routes will perform different actions. In Nest, you can use built-in decorators to customize elements like Request, Response, Controller, Route, action, and so on. Thus, the repetitive code is removed and the development efficiency is improved. As follows:

// omit the dependency @controller ('cats') // Module name, used to define route prefix: /catsexportClass CatsController {// Change the status code of the route to 204@httpCode (204) // change the response Header @header ('Cache-Control'.'none'// Modify the route ([POST] /cats) request method: Create (@body () createCatDto: @body () createCatDto: @body () createCatDto: CreateCatDto) {// Returns data for the responsereturn 'This action adds a new cat';
  }

  @Redirect("http://www.fzzf.top"// Route redirection @get (":id")                       // [GET] /cats/:id
  show(@Query("") query: ListAllEntities) {
    // do something
  }
}
Copy the code

For controller returns, Nest supports promises returned with async/await and Observable Streams returned with RxJS, as shown in the following example:

import { of } from 'rxjs'; // controller code omitted @put (":id")
async update(@Param() params: UpdateCatDto): Promise<any> {
    returnthis.catsService.update(params); } // Async @get () index(@query () Query: IndexCatDto): Observable<any[]> {return of(this.catsService.find(query));
}
Copy the code

Provider

In Nest, a provider is defined as a class that has a set of methods for a particular function. The @Injectable() decorator makes it a service that can be injected into the Nest Module as a dependency. Between providers in the same scope, one provider can also be injected as a dependency into the other provider. The following code looks like this:

// cats.service
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(
    private readonly otherService: OtherService
  )

  async create(values: any): Promise<any> {
    returnthis.otherService.create(values); }}Copy the code

We can declare a Module (see the next section) and inject the provider into the Controller. The details of dependency injection are done internally within the Nest framework, and here are just the basic dependency injection postures, as shown in the following code:

// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service'; @Module({controllers: [CatsController], // Controller providers of the current Module: [CatsService], // Provider})export class CatModule {}

// cats.controller
import { CatsService } from './cats/cats.service';

@Controller('cats') 
export class CatsController {
  constructor(
    private readonlyCatsService: catsService // Dependency injection) {} @post () create(@body () createCatDto: createCatDto) {// Returns data for the responsereturnthis.catsService.create(createCatDto); }}Copy the code

Module

Module is the basic unit of Nest application. Each Nest app has at least one Module (root Module). To put it simply, in Nest, n functional modules form a business module through dependency injection, and N business Modules form a Nest application. Both the Controller and provider need to be registered with the Module to complete dependency injection. The following code describes the basic Module declaration posture.

@Module({// Dependencies on other Module classes imports: [], // The controller class that business modules need to see how Module is instantiated as a business Module or a function Module controller: [], // provides the dependent class provider: [], // exports the module provider, can be injected by other module dependencies: [],})export class AppModule {}

Copy the code

This allows hierarchical dependencies between modules and providers in different modules to be reused by passing the exports argument. As shown in the following code:

Request.module. ts @ module ({// register provider provider: [RequestService], // export registered Provider exports: [RequestService],})exportClass RequestModule {} // api.module.ts @ module ({// import a RequestModule and Nest will register itexportProvider imports: [RequestModule], // Register Controller Controllers: [ApiController],})export class ApiModule {}

// api.controller.ts
@Controller('api') 
export class ApiController {
  constructor(
    private readonlyDependency injection (dependency injection) {}}Copy the code

In addition, we can decorate Module A as A Global module with the @global decorator, so that any module can inject the export dependency in Module A without import. We can also import modules dynamically with specific parameters.

Middleware

Nest is a superframework, and the bottom layer relies on the Express framework (or HAPI), so the Middleware in Nest is equivalent to the Middleware in Express, and you can use it to access request and response, as well as do some pre – and post-processing. It is declared as follows:

  • The statement
Import {Injectable, NestMiddleware} from'@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class CatchMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('catch... '); next(); }} // Mode 2export functioncatchError(req, res, next) { console.log(`catch... `); next(); };Copy the code
  • use
// mode 1 @Module({imports: [CatsModule],})exportclass AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(CatchMiddleware) .forRoutes(CatsController); }} // const app = await nestfactory.create (AppModule); app.use(catchError); await app.listen(3000);Copy the code

Exception filters

Nest comes with an exception-filter layer that the handler doesn’t handle, as well as friendly exception handling objects. We throw catch global or local exceptions, such as global error, by declaring exception. As shown in the following code:

http-exception.filter.ts

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express'; // if @catch () is null, Catch all types of exceptions @catch (HttpException)exportclass HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) {// Get the current handler's context object const CTX = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); Response.status (status).json({statusCode: status, timestamp: new Date().toisostring (), path: request.url, }); }} // Use @post () @usefilters (new HttpExceptionFilter()) async create(@body () createCatDto: CreateCatDto) { throw new ForbiddenException(); }Copy the code

At the same time, we can inherit the Nest built-in exception object to standardize the format of system exceptions, as shown in the following code:

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN); }} // Error print {"status": 403,
  "error": "This is a custom message"
}
Copy the code

Pipes

Pipe is used in Nest to pre-process requests from handlers in Controller.

  • Format request input, such as req.query, req.param, req.body, etc.
  • Validates the request input, passing it as is if it is correct, or throwing an exception anywayexception; You can refer to the declaration hereThe official documentation.

Guards

In Nest, Guard is designed to have a single blame pre-handler that informs Route if the corresponding handler needs to be executed. The official documentation recommends using Guard for authorization-related functions, such as permission validation and role validation, instead of using Middleware in traditional Express applications.

Middleware is officially considered inherently “dumb” because it has no way of knowing which handler will be executed after next is called. Guard can use ExecutionContext to get the ExecutionContext of the current request and know which handler is going to execute, thus ensuring that the developer adds processing logic in the correct request or response cycle.

It is declared as follows:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs'; // Notice the function declaration @injectable ()export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    returnvalidateRequest(request); }}Copy the code

The Guard also supports global, single controller, or single Handle Method preprocessing. Please refer to the official documentation for details.

Interceptors

In the Nest, the Interceptor design inspiration comes from [AOP] (https://en.wikipedia.org/wiki/Aspect-oriented_programming), Used to add pre/post operations to a handler module, such as controller. Implement functions such as additional operations on input/output, filtering of exceptions, overloading the current handler logic under certain conditions, and so on. It is declared as follows:

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

@Injectable()
exportclass HttpInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> {// context Current execution context // next next handler to executereturnnext.handle().pipe( map((value: any) => ({ code: 0, data: value, })) ); UseInterceptors(HttpInterceptor)export class CatsController {}
Copy the code

Request Lifecycle

The various Nest components described above are registered and managed by the Nest framework, and they are executed during a claim cycle phase of a request. The specific execution sequence is as follows:

  1. Request input
  2. globalmiddleware
  3. The root module (root module)middleware
  4. globalGuards
  5. Controller Guards
  6. routeUsed on the corresponding handler function@UseGuards()registeredGuard
  7. globalinterceptors(Controller pre-processing)
  8. controllertheinterceptors(Controller pre-processing)
  9. routeUsed on the corresponding handler function@UseInterceptors()registeredinterceptors(Controller pre-processing)
  10. globalpipes
  11. controllerthepipes
  12. routeUsed on the corresponding handler function@UsePipes()registeredPipes
  13. routeRegistered for the handler function parameterPipes(such as: @ Body (new ValidationPipe ())
  14. routeCorresponding processing function
  15. routeCorresponding to handler function dependenciesSerivce
  16. routeUsed on the corresponding handler function@UseInterceptors()registeredinterceptors(Controller post-processing)
  17. controllertheinterceptors(Controller post-processing)
  18. globalinterceptors(Controller post-processing)
  19. routeRegistered for the handler functionException filters
  20. controllerregisteredException filters
  21. Globally registeredException filters
  22. The response output

The resources

  • docs.nestjs.com/
  • Reactivex. IO/RXJS/class /…