Nestjs, which has been around for a while now, has a Java Spring feel to it. Nestjs can use all of the middleware in Express. In addition, it has perfect typescript support and can be used in conjunction with typeorm, a database relational mapping, to quickly write an interface gateway. This article introduces the features and benefits of being an enterprise-class Node framework.

  • Start with dependency injection (DI)
  • Decorators and annotations
  • Nestjs’s “Onion Model”
  • Summary of nestJS features

Originally posted on my blog: github.com/fortheallli…

Welcome star and Fork

Start with dependency injection (DI)

Dependency injection in Angular

Since Angular1. x, dependency injection or inversion of control has been implemented. Angular1. x has controllers, Services, and modules. I wrote about Angular1.3 for a while back. Here’s an example:

var myapp=angular.module('myapp'['ui.router']);
myapp.controller('test1'.function($scope,$timeout){}
myapp.controller('test2'.function($scope,$state){}

Copy the code

This is an example of dependency injection from Angular1.3. First you define a module named “myApp”, and then you define the Controller controller in the myApp module. Gives control of the myApp module to the myapp. Controller function. The flow chart of dependency injection is as follows:

How the module myApp is defined is determined by its two controllers and depends on services such as Scope and timeout in the controller. This enables dependency injection, or inversion of control.

What is dependency injection

Use an example to give a general idea of what dependency injection is.

class Cat{}class Tiger{}class Zoo{
  constructor() {this.tiger = new Tiger();
     this.cat = newCat(); }}Copy the code

In the above example, we define Zoo and instantiate Cat and Tiger from its constructor method. If we want to add an instance variable to Zoo, for example to modify the Zoo class itself, let’s say we want to add an instance variable of Fish to the Zoo class:

class Fish{}

class Zoo{
  constructor() {this.tiger = new Tiger();
     this.cat = new Cat();
     this.fish = newFish(); }}Copy the code

In addition, if we want to modify the variables passed into the Tiger and Cat classes when instantiated in Zoo, we must also modify them on the Zoo class. This kind of repeated modification will make the Zoo class not universal, which makes the function of the Zoo class need to be tested repeatedly.

Let’s imagine passing the instantiation as a parameter to the Zoo class:

class Zoo{
  constructor(options){
     this.options = options; }}var zoo = new Zoo({
  tiger: new Tiger(),
  cat: new Cat(),
  fish: new Fish()
})
Copy the code

We put the instantiation process in parameters passed to the Zoo constructor so that we don’t have to change the code repeatedly in the Zoo class. This is a simple introduction to dependency injection. More fully using dependency injection, we can add static methods and static properties to Zoo classes:

class Zoo{
  static animals = [];
  constructor(options){
     this.options = options;
     this.init();
  }
  init(){
    let _this = this;
    animals.forEach(function(item){ item.call(_this,options); })}static use(module){
     animals.push([...module])
  }
}
Zoo.use[Cat,Tiger,Fish];
var zoo = new Zoo(options);

Copy the code

We use the Zoo static method use to inject the Cat, Tiger, and Fish modules into the Zoo class, handing over the implementation of Zoo to the Cat, Tiger, and Fish modules, as well as the options parameter passed in the constructor.

(3) Dependency injection in NestJS

Nestjs also references dependency injection in Angular, using modules, controllers, and services.

@Module({
  imports:[otherModule],
  providers:[SaveService],
  controllers:[SaveController,SaveExtroController]
})
export class SaveModule {}
Copy the code

This is how to specify a module in NestJS. You can inject other modules in the imports property, services in the Prividers property, and controllers in the imports property.

Decorators and annotations

Nestjs embraces typescript beautifully, especially with its extensive use of decorators and annotations. See my article: Decorators and annotations in typescript for an understanding of decorators and annotations. Let’s see how easy it is to write business code in NestJS with decorators and annotations:

import { Controller, Get, Req, Res } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(@Req() req,@Res() res) { return 'This action returns all cats'; }}Copy the code

The findAll function is defined for the get method of this route. FindAll is actively triggered when a get request is made to /cats.

In addition, the findAll function allows you to use the request request and the response to the request directly within the topic with the req and res arguments. For example, we use req to get the parameters of the request and res.send to return the result of the request.

Iii. Nestjs’s “Onion Model”

Here’s a quick look at how layering works in NestJS, that is, how requests are layered once they arrive at the server until the request is responded to and the result returned to the client.

In NestJS, based on service, The level of processing is supplemented by middleware, Exception filters, Pipes, Guards, and interceptors, which provide a layer of processing between the request and the actual handler.

The logic in the figure above is the process of hierarchical processing. After hierarchical processing, the request can reach the server processing function. Next, we introduce the specific role of the hierarchical model in NestJS.

(1) Middleware

Middle in NestJS is exactly like Express middleware. Not only that, we can also use middleware directly in Express, for example in my application that needs to handle core cross-domain:

import * as cors from 'cors';
async function bootstrap() {
  onst app = await NestFactory.create(/* Create app business logic */)
  app.use(cors({
    origin:'http://localhost:8080'.credentials:true
  }));
  await app.listen(3000)
}
bootstrap();

Copy the code

In the code above we can use the middleware of Core Express directly via app.use. So that the server side support core cross domain.

In addition, the middleware with NestJS fully retains the characteristics of the middleware in Express:

  • Response and Request are accepted as parameters in the middleware, and the request object request and result return object Response can be modified
  • You can end the processing of the request and return the result of the request directly, that is, res.send and so on in the middleware.
  • After the middleware completes processing, if the request result is not returned, the middleware can be passed on to the next middleware processing using the next method.

In NestJS, the middleware is exactly the same as in Express. In addition to being able to reuse Express middleware, it is also very convenient to use middleware in NestJS for a specific purpose:

class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats'); }}Copy the code

This is using LoggerMiddleware for specific routing urls with /cats.

(2) Exception filters

Exception filters can catch exceptions that are thrown at any stage of the backend processing, and then return the Exception result to the client (e.g. return error codes, error messages, etc.).

We can define a custom exception filter, and in this exception filter can specify which exceptions to catch, and for those exceptions should return what results, etc., an example of a custom filter used to catch HttpException exceptions.

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url, }); }}Copy the code

We can see that host implements ArgumentsHost. Information in the runtime environment can be obtained from host, request and response can be obtained from HTTP request, and client and data can be obtained from socket.

Similarly, for exception filters, we can specify that they are used in a module, globally, etc.

(3), Pipes

Pipes Common user to verify whether the parameters in the request meet the requirements, play a function of verifying parameters.

For example, we need to check or convert the type of some parameters in a request:

@Injectable()
export class ParseIntPipe implements PipeTransform<string.number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    returnval; }}Copy the code

The above ParseIntPipe converts arguments to decimal integer numbers. We can use it like this:


@Get(':id')
async findOne(@Param('id'.new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}

Copy the code

For the parameter ID in the GET request, the new ParseIntPipe method is called to convert the parameter ID to a decimal integer.

(4) Guards

Guards, which determine whether a request should be accepted and processed by a handler, can also be used in middleware, where Guards provide more detailed information about the execution context of a request than middleware.

Typically the Guards guard layer, located behind middleware, before the request is formally processed by the handler.

Here is an example of Guards:

@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 context implements an ExecutionContext interface with rich ExecutionContext information.


export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost;
  switchToWs(): WsArgumentsHost;
}

export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}

Copy the code

In addition to the information in ArgumentsHost, ExecutionContext also contains the controller that the getClass user gets to handle for a particular route. GetClass is used to get the handler function that returns background processing for the specified route.

For Guards handlers, if true, the request will be processed normally, and if false, the request will throw an exception.

(5) Interceptors

An interceptor can bind to each function that needs to be executed, and the interceptor will run before or after the function is executed. You can convert the result returned after the function is executed.

To summarize:

Interceptors can be run before or after a function is executed. If it is run after the function is executed, it can intercept the result of the function execution, modify parameters, and so on.

Here’s another example of timeout handling:

@Injectable()
export class TimeoutInterceptor implements NestInterceptor{
  intercept(
    context:ExecutionContext,
    call$:Observable<any>
  ):Observable<any>{
    return call$.pipe(timeout(5000)); }}Copy the code

The interceptor can be defined on the controller and can handle timeout requests.

Fourth, the characteristics of NEstJS summary

Finally, summarize the advantages and disadvantages of NestJS.

Advantages of NestJS:

  • Perfect support for typescript, so you can use the growing TS ecosystem tools

  • Compatible with Express middleware, because Express is the earliest lightweight Node server framework, NestJS can take advantage of all express middleware to complete its ecosystem

  • Layer on layer, to some extent, to constrain the code, such as when to use middleware, when to use Guards guards, etc.

  • The idea of dependency injection and modularization provides a complete MVC link, making the code structure clear and easy to maintain. Here, M refers to the data layer can be injected in the form of modules, such as the entity of TypeOrM can be injected into modules.

  • Perfect RXJS support