Nest 基于 Express 或 Fastify 封装 HTTP 请求和路由,并支持其他 web 框架/库,并直接暴露他们的接口,使得开发者可以自由使用第三方库。Nest 使用 TypeScript 来编写程序,用了很多 TS 的写法如装饰器等特性,结合了 OOP、FP、FRP 的相关理念,设计上很多灵感来自于 Angular 或者后端常用的 Java 技术栈 Spring 框架,如依赖注入、面向切面编程(AOP)等,可以说 Nest 是 Node.js 版的 Spring 框架。
1、概述
Nest.js 基于 express 这种 http 平台做了一层封装,应用了 MVC、IOC、AOP 等架构思想。
MVC 就是 Model、View Controller 的划分,请求先经过 Controller,然后调用 Model 层的 Service、Repository 完成业务逻辑,最后返回对应的 View。
IOC 是指 Nest.js 会自动扫描带有 @Controller、@Injectable 装饰器的类,创建它们的对象,并根据依赖关系自动注入它依赖的对象,免去了手动创建和组装对象的麻烦。
AOP 则是把通用逻辑抽离出来,通过切面的方式添加到某个地方,可以复用和动态增删切面逻辑。
2、分层架构
2、核心概念
- Controller
- Provider
- Module
- Middlewares
- Exception filters
- pipe 管道
- Guards 守卫
- Interceptors 拦截器
3、详细描述
1. Controller
- 负责接收请求,返回响应。与路由系统配合,路由系统决定那个请求使用那个 controller,期间可以调用 service。
- 具体实现
/* cats.controller.ts */
import { Controller, Get } from "@nestjs/common";
@Controller("cats")
export class CatsController {
@Get()
findAll(): string {
return "This action returns all cats";
}
}
2. Provider
Providers 是 Nest 的一个基本概念。许多基本的 Nest 类可能被视为 provider - service, repository, factory, helper 等等。 他们都可以通过 constructor 注入依赖关系。 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest 运行时系统。 Provider 只是一个用 @Injectable() 装饰器注释的类。
- 具体实现
//cats.service.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;
}
}
//cats.controller.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 {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
3. Module
- Nest 采用模块化思路,将 APP 分割成各个模块,可以是专属功能的模块,也可以是通用的共享模块以及全局模块。APP 至少有一个根模块,以及 imports 及 exports。一个模块可以引用其他模块,也可以被其他模块引用。exports 可以导出 providers(service)供其他模块使用
- 具体实现
import { Module } from "@nestjs/common";
import { CatsController } from "./cats.controller";
import { CatsService } from "./cats.service";
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
4. Middlewares
- 中间件是在路由处理程序之前调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。 next() 中间件函数通常由名为 next 的变量表示
- 具体实现
//logger.middleware.ts
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();
}
}
//app.module.ts
import {
Module,
NestModule,
RequestMethod,
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({ path: "cats", method: RequestMethod.GET });
}
}
5. Exception filters
-
Nest 框架内部提供一个异常处理机制,专门用来负责应用程序中未处理的异常。当错误未被捕获,就会被 Nest 的全局过滤异常器处理。这种机制跟 Express 差不多 。跟 Express 不同的是,Nest 内部提供了很多类型的错误类,用来抛出具体的错误。Nest 同样还可以自定义错误处理类。
-
具体实现
// 声明
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
// 使用 controller.ts
@Get()
async findAll() {
throw new ForbiddenException();
}
// 可以在controller 中绑定异常过滤处理器,也可以绑定在方法作用域、控制器作用域、全局作用域
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
6. pipe 管道
- Nest 中的 pipe 管道是用来为路由处理函数的参数(输入数据)做转换和校验,因此在路由处理函数之前被调用,然后将处理后的数据传给路由处理函数当作参数.
- 具体实现
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
// 声明
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
//使用1
@Post()
async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
//使用2
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
//使用3
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
7. Guards 守卫
-
服务端应用程序另外一个常见的功能就是认证,例如权限、角色等。Nest 内置的 Guards 是专门用于做这些事情的.
-
Guards 执行顺序是在所有的中间件执行完之后,在任何 interceptor 或者 pipe 之前执行
-
具体实现
// 定义
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;
}
}
// 使用
@Controller("cats")
@UseGuards(RolesGuard)
export class CatsController {}
8. Interceptors 拦截器
拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:
-
在函数执行之前/之后绑定额外的逻辑
-
转换从函数返回的结果
-
转换从函数抛出的异常
-
扩展基本函数行为
-
根据所选条件完全重写函数 (例如, 缓存目的)
-
具体实现
// 定义
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 {}
4、附录
1. ORM/ODM
ORM(Object/Relational Mapping) 对象/关系 映射 ODM(Object/Document Mapping) 对象/文档 映射
- 面向对象编程和关系型数据库,都是目前最流行的技术,但是它们的模型是不一样的。 面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术。
2. DTO
数据传输对象(DTO)(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。数据传输目标往往是数据访问对象从数据库中检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以不具有任何行为除了存储和检索的数据(访问和存取器)。
根据定义,我们需要在代码中约定一下 DTO,还是以注册接口为例,先创建 user.dto.ts
简单定义一下:
// src/logical/user
exportclass RegisterInfoDTO {
readonly accountName: string | number;
readonly realName: string;
readonly password: string;
readonly repassword: string;
readonly mobile: number;
}
其实就是输出了一个类似于声明接口的 class,表明了参数名和类型,并且是只读的。
当然,Nest 支持使用 Interface(接口) 来定义 DTO,具体语法可以浏览 TypeScript 官方文档,不过 Nest 建议使用 Class 来做 DTO(就踩坑经验而言, Class 确实比 Interface 方便多了),所以 Interface 在这里就不多介绍了。
3. Nest CLI
Nest CLI 是一个命令行界面工具,以帮助您初始化、开发和维护 Nest 应用程序。它以多种方式提供帮助,包括搭建项目、以开发模式为其提供服务,以及为生产分发构建和打包应用程序。它体现了最佳实践的架构模式,以构建良好的应用程序。
//安装脚手架
npm i -g @nestjs/cli
/创建项目
nest new project-name
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
app.controller.ts 带有单个路由的基本控制器示例,用于编写路由
app.controller.spec.ts 对于基本控制器的单元测试样例
app.module.ts 应用程序的根模块。
app.service.ts 带有单个方法的基本服务,主要编写接口返回什么数据。
main.ts 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。类似于vue的main.ts
4. typeorm、sequelize 对比
参考
Nest.js — Architectural Pattern, Controllers, Providers, and Modules