初识Nest

101 阅读5分钟

前言

Nest 是一个用于构建 Nodejs 服务器端应用程序的框架。内置完全支持 Typescript, 其底层使用的 HTTP Server 框架为:Express(默认) 和 Fastify

Nest 是在这些框架之上提供了一些抽象。

如果你想简单写一些后端服务,接口交互可以使用 Express。但如果想要写出更易于维护的应用,使用 Nest 会是一个不错的选择。

安装

# 安装脚手架
yarn global add @nestjs/cli

# 创建项目
nest new projectName

一、 5 种 HTTP 数据传输方式

目前来说与后端通过 http 通信主要有以下 5 种方式:

1. url param

/api/get/:id 前端将参数直接拼接在 URL 后面

2. query

/api/get?id=111&name=pnm 前端使用 ? 和 & 拼接参数 其中这种情况,如果遇到中文或者特殊字符需要经过编码,可以使用 encodeURIComponent 或者 query-string 库来处理

3. json

这应该是大家最常用的 POST 请求方式了。只要指定了 content-type: application/json 之后,前端就可以直接将对象传入。

4. form-urlencoded

jsonquery 类似,但挺少用到的。json 是将整个对象放入 请求体 中,而他是将 对象进行 query 的那种处理(&拼接参数) 之后再放入 请求体 的。指定了 content-type: application/x-www-form-urlencoded

5. form-data

如果是传输文件类,会经常用到这个。需要指定 content-type: multipart/form-data

二、 后端对象关系

后端系统中,几个重要的对象:

1. Controller 对象 接收 http 请求,调用 Service 返回响应

2. Service 对象 实现业务逻辑

3. Repository 对象 实现对数据库的 CRUD

4. DataSource 对象 建立数据库连接

5. Config 对象 用户信息等配置对象

-- 关系链 --

Controller 依赖了 Service 实现业务逻辑 Service 依赖了 Repository 来做增删改查 Repository 依赖 DataSource 来建立连接 DataSource 又需要从 Config 对象拿到用户名密码等信息。

由于这些依赖关系,所以我们需要缕清楚哪个需要先创建,哪个需要后创建

// 配置
const config = new Config({ username: 'xxx', password: 'xxx' })
// 数据库
const dataSource = new DataSource(config)
// 连接数据库
const repository = new Repository(dataSource)
// 业务逻辑操作数据库
const service = new Service(repository)
// 接收 http 请求
const controller = new Controller(service)

IOC(Inverse Of Control)

上述这种手动创建实例并组装对象的方式,显得有些麻烦(虽然我不觉得麻烦 doge),所以便有了更简单的,直接在 class 上声明依赖了啥,然后让工具自己去分析,创建,组装。

程序初始化时会扫描 class 上声明的装饰器,然后把这些 classnew 一个实例放到容器里。且创建实例的时候还会把他依赖的对象注入进去。

主动创建依赖被动等待依赖注入,这便是 IOC ,反转控制。

// 声明 RequestService 有注入和被注入功能
@Injectable()
export class RequestService {}

@Controller('/api/request')
export class RequestController implements OnModuleInit {
  // 在 controll 中可以利用这种方式注入
  constructor(private readonly requestService: RequestService) {}
}

AOP

后端框架基本都是 MVC 架构。 请求会发送给 C(Controller 层) 由它调用 M(Model 层)Service 来完成业务逻辑,最后返回对应的 V(View 层)

    graph LR
    H>http请求] --> C[Controller层]
    C --> M[Model层]
    M --> C
    C --> V[View层]

此外,Nest 还提供了 AOPAspect Oriented Programming 面向切面编程)的能力。为何需要这个?

主要是为了一些通用逻辑,比如:权限,日志,异常的处理。因为是通用逻辑,所以放到 Controller 层做业务逻辑也不太好。所以在 Controller 层 之前之后,切一刀专门用作通用逻辑的处理,这便是 面向切面编程

其实就是抽离公共逻辑的感觉。

Nest 实现 AOP 的方式一共有五种,包括

  1. Guard(路由守卫,权限判断)

  2. Pipe(管道,进行参数校验、转换)

  3. Interceptor(请求响应的拦截器)

  4. ExceptionFilter(异常处理)

  5. Middleware(中间件)

MiddlewareInterceptor 功能类似,但也有不同, Interceptor 可以拿到目标 class、handler 等,也可以调用 rxjsoperator 来处理响应,更适合处理具体的业务逻辑

Middleware 更适合处理通用的逻辑

    graph LR
    H>http请求] --> M[中间件] --> I(请求拦截) --> G((守卫))

    G --> P{管道} --> IR(响应拦截) --> ERR([异常处理]) --> R>http响应]

三、 Nest 原理

Nest 最主要的是帮用户自动创建对象并注入依赖。那么这些是如何做到的?

简单介绍下 Reflect 的 API

// 在 target 上定义元数据 metadataKey ,值为 metadataValue
Reflect.defineMetadata(metadataKey, metadataValue, target)

// 获取 target 上元数据 metadataKey
let result = Reflect.getMetadata(metadataKey, target)

且 Reflect 可以使用装饰器语法

// 如下语法会给 MyName 定义一个 name 元数据,值为 王尼玛
@Reflect.metadata('name', '王尼玛')
class MyName {}

// 然后我们可以将
function Name(name: string) {
  return Reflect.metadata('name', name)
}

@Name('牛尼玛')
class MyName {}

// 然后通过分析这个类,我们可以获取到 @Name 中的元数据的值。

我们用一个 Nest 里的例子来分析。

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}
}

// 我们去 Controller 里瞅瞅源码,会发现是帮这个 CatsController 添加了这些元数据
function Controller(prefixOrOptions) {
  // ...省略
  return (target) => {
    Reflect.defineMetadata('__controller__', true, target)
    Reflect.defineMetadata('path', path, target)
    Reflect.defineMetadata('host', host, target)
    Reflect.defineMetadata('scope:options', scopeOptions, target)
    Reflect.defineMetadata('__version__', versionOptions, target)
  }
}

// 等到后面要用的时候,就会取出元数据

但是,构造函数里的参数,是如何被赋值进去的呢???

虽说我们知道 TS 里,用修饰符修饰的构造函数参数,会相当于 this.catsService = catsService,但是 元数据 这么多,Nest 是怎么知道,我这个构造函数里需要放进去 CatsService 类型的呢?构造函数这里也没有声明元数据呀,如何做到的??

这里就用到了 TSemitDecoratorMetadata 特性,该特性帮助 Nest 自动添加了 design:paramtypes design:returntype design:type 这三个元数据。

这三个元数据分别表示: 参数类型返回值类型类型(我们是函数,所以类型是 Function),而这个 参数类型 ,值便是我们的 CatsService

Nest 就是通过这样知道的。

总结核心原理为:

使用了 Reflectmetadata API,该 API 还处于草案阶段,所以 nest 用的是名为 reflect-metadata 的 polyfill 包。

利用这个包,通过装饰器给 class 或者 对象 添加 元数据, 并且开启 TS 的 emitDecoratorMetadata 来自动添加类型相关的 metadata ,然后运行时通过这些元数据实现依赖扫描,对象创建等功能。