Nest is a NodeJS server-side framework. Unlike Express/Koa/Fastify, which does not have a (weak) proposition and provides a pure idea of HTTP server encapsulation and asynchronous flow string parallelism, NestJS provides an architectural proposition and has its own set of architectural patterns that require developers to organize their code according to the architecture required by NestJS. Suitable for teams to build efficient, reliable, highly scalable, loosely coupled, and easily testable large NodeJS server applications.

In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications. This has given rise to awesome projects like Angular, React and Vue, which improve developer productivity and enable the creation of fast, testable, and extensible frontend applications. However, while plenty of superb libraries, helpers, and tools exist for Node (and server-side JavaScript), none of them effectively solve the main problem of – Architecture. Nest provides an out-of-the-box application architecture which allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications. The architecture is heavily inspired by Angular. — Documentation | NestJS – A From progressive Node.js Framework, you can see that node.js or JavaScript frameworks do things in their own right, and they probably do things well. But overall there is no framework, or architecture, for stringing together good things into a common pattern. — keelii

Nest encapsulates HTTP requests and routing based on Express or Fastify, and supports other Web frameworks/libraries and exposes their interfaces directly, giving developers the freedom to use third-party libraries. Nest uses TypeScript to write programs, uses a lot of TS writing methods such as decorators and other features, combined with OOP, FP, FRP related concepts, design a lot of inspiration from Angular or common backend Java stack Spring framework. Nest is the Node.js version of the Spring framework.

Support for using scaffolding CLI to generate project architecture

npm i -g @nestjs/cli
nest new project-name
Copy the code

Project directory

src
- app.controller.spec.ts
- app.controller.ts
- app.module.ts
- app.service.ts
- main.ts
Copy the code

Layered architecture

The architecture advocated by NestJS is not consistent with traditional MVC. Nest pays more attention to back-end logic (controller, service and data). The view layer has no requirements and is relatively independent and can be customized by users. In MVC architecture, the division of labor between Model layer and Controller layer becomes unclear as the code becomes larger and more logical, which makes it difficult to maintain. Therefore, Nest draws on the layered design of many traditional back-end frameworks.

The Controller layer is responsible for processing the request and returning the response. A Controller receives and processes a specific request, and the routing system distributes the request to each Controller for processing.

2. The Service layer is responsible for providing methods and operations and contains only the business logic. For example, CRUD operates on application data, creates a new user, and so on. Nest uses the Angular (Spring) dependency injection model to inject a Service into a Controller as a service. The Controller and Service can then be completely decoupled: What a Controller does is to receive requests and call the Service when appropriate. It doesn’t care how the Controller is implemented inside the Service, and it doesn’t need to change with the changes of the Service to achieve decoupling effect.

3. The Data Access layer is responsible for accessing Data in the database, such as using Object Document Mapping (ODM) such as Mongoose to Access database Data.

The core concept

1, the Controller

Responsible for receiving requests and returning responses. In cooperation with the routing system, the routing system decides which Controller to use for that request and calls the Service in between.

Nest uses the DECORator syntax of TS to encapsulate the routing system and provide interfaces, so that the controller can be easily combined with the routing system. Compared with the frame-based centralized routing like Express, Nest routing is decentralized, and the combination with controller is very convenient:

import { Controller, Get, Post } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() create(): string { return 'This action adds a new cat'; } @Get('ab*cd') findAll(@Req() request: Request): string { return 'This action returns all cats'; }}Copy the code

You can use decorators for classes, methods, and parameters. Use the @Controller decorator to combine the class as a Controller with the route, use @get / @POST as the request method handler for the method, and use @req /@Query to access the request and all parts of the data in the request for the method parameters.

2, the Provider

A Provider is a class that can inject dependencies into another class, such as Services, repositories, factories, helpers, and so on. Equivalent to the Service layer that provides services. Providers are concepts that come from dependency injection.

Dependency injection:

Modularity is a way of dividing code, and dependency injection is a design pattern that connects modules and manages dependencies between them to achieve high cohesion and low coupling between modules. Dependency injection are common in object-oriented programming, in object-oriented programming, introducing the dependence is usually A class, when used will create an instance of the class to use, so that when A class A dependent on another class B, regular, A bad writing is hard-coded dependencies, that is, class A introduction of class B and create an instance of B in A, However, when the way instances of B are created changes, A has to change with B. With hard-coded dependencies, the more dependencies A introduces, the worse this coupling becomes. In addition, dependent instances are created repeatedly.

At this point, A third-party dependency management container can be introduced. The container will first contain registered dependencies. When A depends on B, the container will find AND instantiate B, inject an instance of B directly into A, and A can directly use instance B without instantiating B internally. This solves the coupling and repeated instantiation problems in hard-coded dependencies and makes each module easy to test.

Nest controllers often call Services to implement business logic, so Nest uses dependency injection to ensure low coupling between the two layers. Create and use a service in Nest:

Ts import {Injectable} from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { return this.cats; Ts import {controller, Get, Post, Body} from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; @Controller('cats') export class CatsController { // Notice the use of the private syntax. // This shorthand allows us to both declare and initialize the catsService member immediately in the same location. constructor(private catsService:  CatsService) {} @Post() async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() async findAll(): Promise<Cat[]> { return this.catsService.findAll(); }}Copy the code

3, the Module

Nest adopts a modular approach to divide APP into modules, which can be exclusive function modules, common shared modules and global modules. APP has at least one root module, which contains defined providers, controllers, Repositories, Interceptors, middleware, and so on, as well as imports and exports. A module can reference and be referenced by other modules. Exports exports providers for use by other modules

APP is composed of Modules, which are layered by Controller and Service, with clear code organization and responsibility division. This modular organization allows us to divide the entire APP into modules according to the logical domain, reducing the coupling between codes and enhancing scalability. Domain-driven Development can be combined.

Declare a module:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
Copy the code

4, middleware

The bottom layer of Nest processes HTTP requests based on Express and retains the Middleware mechanism of Express. Therefore, the positioning, functions and interfaces of Nest’s middleware are the same as those of Express, but the difference is that The middleware registered with Nest is called before the routing handler (such as Controller), whereas in the case of Express, the middleware is the routing handler.

Nest declares middleware, like a provider, as a service:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}
Copy the code

Register middleware: Use the configure function provided by the NestModule interface to configure middleware for the Module or route.

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); // .forRoutes('cats/:catName'); ForRoutes ({path: 'cats', method: requestmethod.get}); //forRoutes({path: 'ab* CD ', method: requestMethod.all}); // To use middleware for different controllers, you can also pass a comma-separated list of controllers //.forroutes (CatsController); // exclude(//{path: 'cats', method: requestmethod. GET}, //{path: 'cats', method: RequestMethod.POST }) } }Copy the code

The location of the registered middleware is relatively top level compared to Express’s location in a specific route because it is convenient to use foreRoutes to configure middleware for multiple controllers and multiple routes because the middleware is not specific to a specific route.

5, and Exception filters

Nest provides an exception handling mechanism within the framework that takes care of unhandled exceptions in your application. When an error is not caught, it is handled by Nest’s global filter exception. This mechanism is similar to Express. Unlike Express, Nest provides many types of error classes internally that can be used to throw specific errors. Nest can also customize error handling classes:

// Declare export class ForbiddenException extends HttpException {constructor() {super('Forbidden', httpstatus.forbidden); }} // use controller.ts @get () async findAll() {throw new ForbiddenException(); }Copy the code

By defining and implementing the ExceptionFilter interface provided by Nest, you can customize an ExceptionFilter to get information such as Request and error context:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @catch (HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { 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, }); }}Copy the code

Then bind the exception filter handler in controller, or in method scope, controller scope, or global scope

@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
Copy the code

6. Pipe

The PIPE pipe in Nest is used to convert and validate the arguments (input data) of the routing handler, so it is called before the routing handler and passes the processed data to the routing handler as arguments.

Why is there such a thing?

As a server-side application, one of the most frequently written functions is request data processing and validation. Although Nast uses TypeScript, it can only do static checking of code before it runs, not input checking at runtime. Some tripartite libraries such as GraphQL, IO-TS, and Joi can do runtime checking by writing scheme. The pipes in Nest do the conversion and inspection of input data from the routing processor at run time.

We can write the conversion and validation directly in the routing handler, but this does not meet the single responsibility principle (the routing handler does extra work); You can write a separate class to encapsulate, but each route handler function is called; You can write a Nest middleware, but Nest middleware is APP Module level, and the parameter handling and validation of a particular routing processor is very specific. So Nest built the Pipe mechanism specifically to handle this logic.

Translation:

// Use the built-in ParseIntPipe converter @get (':id') async findOne(@param ('id', ParseIntPipe) id: number) { return this.catsService.findOne(id); } // Custom import {PipeTransform, Injectable, ArgumentMetadata, BadRequestException} from '@nestjs/common'; @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'); } return val; }} // Use can be placed at the parameter level, @get (':id') async findOne(@param ('id', new ParseIntPipe()) id) { return this.catsService.findOne(id); }Copy the code

Check:

Import {PipeTransform, Injectable, ArgumentMetadata, BadRequestException} from '@nestjs/common'; import {PipeTransform, Injectable, ArgumentMetadata, BadRequestException} from '@nestjs/common'; import { ObjectSchema } from 'joi'; @Injectable() export Class JoiValidationPipe implements PipeTransform {constructor(private schema: ObjectSchema) {} transform(value: any, metadata: ArgumentMetadata) { const { error } = this.schema.validate(value); if (error) { throw new BadRequestException('Validation failed'); } return value; } // Use @post () // at the parameter level, or use UsePipe at the method level @usepipes (new JoiValidationPipe(createCatSchema)). async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } // CatScheme export class CreateCatDto { name: string; age: number; breed: string; } const createCatSchema = { name: Joi.string().required(), age: Joi.number().required(), breed: Joi.string().required(), } // Save class-validator class-transformer scheme import {IsString, IsInt } from 'class-validator'; export class CreateCatDto { @IsString() name: string; @IsInt() age: number; @IsString() breed: string; Import {PipeTransform, Injectable, ArgumentMetadata, BadRequestException} from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; @Injectable() export class ValidationPipe implements PipeTransform<any> { async transform(value: any, { metatype }: ArgumentMetadata) { if (! metatype || ! this.toValidate(metatype)) { return value; } const object = plainToClass(metatype, value); const errors = await validate(object); if (errors.length > 0) { throw new BadRequestException('Validation failed'); } return value; } private toValidate(metatype: Function): boolean { const types: Function[] = [String, Boolean, Number, Array, Object]; return ! types.includes(metatype); }} @post () async Create (@body (new ValidationPipe()) createCatDto: CreateCatDto, ) { this.catsService.create(createCatDto); }Copy the code

Pipes can be synchronous or asynchronous;

Pipes can be parameter level, method level, Controller level, and entire APP level, which is very flexible.

Guards 7

Another common feature of server applications is authentication, such as permissions, roles, and so on. The Guards built into Nest are designed to do just that, deciding whether a request will be processed by a routing processor. In a middleware framework like Express, this is usually done by middleware because the authentication information in the request is not strongly associated with the specific route, but one of the disadvantages of middleware is that it does not know what the next processor is, does not know what the operation will be, and does not have a lot of sequencing and control. While Nest Guards can get the context information and know what to do next; second, they are placed in a fixed position; third, the declarative code is easier to understand and clear, which is specially used for authentication.

Guards executes after all middleware executes, before any interceptor or pipe.

Definition and Use:

/ / definition: Import {Injectable, canActivate, ExecutionContext} from '@nestjs/common'; import {Injectable, canActivate, ExecutionContext} from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { return true; } // Use @controller ('cats') @useGuards (RolesGuard) export class CatsController {} Import {Injectable, CanActivate, ExecutionContext} from '@nestjs/common'; import {Injectable, CanActivate, ExecutionContext} from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(Context: ExecutionContext): Boolean {// Get the metadata of the route handler bound to this guard; GetHandler gets the route handler to be called, const roles = this.reflector. Get <string[]>('roles', context.gethandler ()); if (! roles) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; return matchRoles(roles, user.roles); @post () @setmetadata ('roles', ['admin']) @useGuards (RolesGuard) async Create (@body () createCatDto: CreateCatDto) { this.catsService.create(createCatDto); }Copy the code

Guards returns true/false, if false, or throws a ForbiddenException;

Guards can be method level, Controller level, and entire APP level.

Interceptors are Interceptors

Aspect – oriented programming is a supplement to object-oriented programming. Backend application is layered architecture commonly, in the object-oriented world, each function was divided into classes, but there are some different classes can use common operations such as log, authentication and transaction management (referred to as cross focus), will be repeated in each application, each layer, call in each class, even if the general operating encapsulate provides a class alone, It will also be coupled with other classes, and the invasion is still strong, resulting in linkage of changes. Section-oriented programming is about separating these cross-concerns, encapsulating them individually as a function, and then dynamically cutting into a point of execution when the code is compiled or run.

Benefits:

1. The same concerns can be removed for separate maintenance and repeated use;

2, not other functions have coupling and invasion type;

3, can be configured to decide whether to inject/disable injection;

The Interceptor in Nest is used to provide a tangential programming mechanism that can be used as a pointcut near the execution of a routing handler function:

  • Bind additional logic before/after function execution

  • Converts the return value of a function

  • The exception thrown by the conversion function

  • Extend the behavior of the underlying function

  • Rewrite a function completely based on specific conditions (e.g., caching)

The key technique is to get the pointcut. Nest uses the RxJS Observable to get the pointcut:

Import {Injectable, NestInterceptor, ExecutionContext, CallHandler} from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before... '); const now = Date.now(); return next .handle() .pipe( tap(() => console.log(`After... ${Date.now() - now}ms`)), ); } @useInterceptors (LoggingInterceptor) export class CatsController {}Copy the code

Next.handle is the route handler to be called. If handle() is not called in the Intercept () method, the route handler will not be executed and the call will be executed.

Pipe represents the result returned by the route handler, which can be processed using a series of manipulation functions provided by the RxJS Observable.

conclusion

1, the custom filters/pipe/apps/interceptor needs to implement a specific interface

2, filters/pipe/apps/interceptor can function level is, the scope of the controller level and global level

3, filters/pipe/apps/interceptor execution order

The appendix

1, the ORM/ODM

Relational Mapping (ORM) Object/Relational Mapping

Object/Document Mapping (ODM) Object/Document Mapping

Object-oriented programming and relational databases are two of the most popular technologies, but their models are different. Object-oriented programming regards all entities as objects, while relational database uses relation between entities to connect data. ORM is a technique to complete the operation of relational database through the syntax of instance object.

ORM uses objects and encapsulates database operations, so it doesn’t touch the SQL language. Developers only use object-oriented programming and interact directly with data objects, regardless of the underlying database.

ODM is a technology to complete the operation of document database through the syntax of instance objects, such as Mongoose.

Read More

Nest.js — Architectural Pattern, Controllers, Providers, and Modules.

Why I choose NestJS over other Node JS frameworks

Thinking of Nest. Js as an enterprise

Bulletproof node.js project architecture 🛡️

Documentation | NestJS – A progressive Node.js framework

Nestjs Framework Tutorial (Part 1: Introduction)