Nest

前言

近几年,由于 Node.js 的广泛流行,JavaScript 已经成为前端和后端应用程序的「通用语言」,从而产生了像AngularReactVue等令人耳目一新的项目,这些项目提高了开发人员的生产力,使得可以快速构建可测试的且可扩展的前端应用程序。然而,在服务器端,虽然有很多优秀的库、helper 和 Node 工具,但是它们都没有有效地解决主要问题 - 架构。

Nest 旨在提供一个开箱即用的应用程序体系结构,允许轻松创建高度可测试、可扩展、松耦合且易于维护的应用程序。

Nest 是用于构建高效且可扩展的服务器端应用程序的渐进式 Node.js 框架,深受 Spring 和 Angular 的启发。它兼容 TypeScript 和纯 JavaScript,并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程),底层默认使用 Express,但也提供了与其他各种库的兼容(例如 Fastify),可以方便地使用各种可用的第三方插件。

Controllers

Controllers 负责接收客户端的请求并返回响应。

Controller 的目的是接收应用的特定请求,路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。为了创建一个基本的控制器,我们使用类和装饰器。装饰器将类与所需的元数据相关联,使得 Nest 可以创建路由映射(将请求绑定到相应的控制器)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @Controller(): Decorator that marks a class as a Nest controller that can receive inbound
* requests and produce responses.
* It defines a class that provides the context for one or more related route handlers that
* correspond to HTTP request methods and associated routes.
*/
@Controller('dicts')
export class DictController {

constructor(private readonly dictService: DictService) {
}

@Post()
async createDicts(@Body() entity: DictEntity): Promise<DictEntity> {
return this.dictService.createDict(entity);
}

}

Providers

Provider 只是一个用 @Injectable() 装饰器注释的纯粹的 JavaScript 类。

Provider 的主要目的是它可以注入依赖项,这意味着对象可以彼此创建各种关系,并且“连接”对象实例的操作在很大程度上可以委托给 Nest 运行时系统。使用 Nest 内置的依赖注入(DI)技术,通过构造函数参数的方式,可以将 Provider 注入到其他类中。

当注入 Provider 时,它必须要在注入的类的模块范围内可见,这可以通过以下几种方式来完成:

  • 在同一模块范围内定义 provider
  • 从一个模块作用域导出 provider 并将该模块导入到要注入的类的模块作用域中
  • 从标记了 @Global() 装饰器的全局模块中导出 provider

在前面的示例中,控制器接收客户端 HTTP 请求并将复杂的业务处理委托给 Provider 来完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Injectable(): Decorator that marks a class as a provider
*/
@Injectable()
export class DictService {

@InjectRepository(DictEntity)
private dictRepository: Repository<DictEntity>;

async createDict(entity: DictEntity): Promise<DictEntity> {
return this.dictRepository.save(entity);
}

}

Dependency injection

依赖,是当类需要执行其功能时,所需要的服务或对象; DI 是一种编码模式,其中的类会从外部源中请求获取依赖,而不是自己创建它们。依赖注入(DI)是一种控制反转(IOC)技术,您可以将依赖的实例化操作委派给IOC容器(Nest运行时系统)来代替自己在代码中进行实例化操作。

Nest 中,借助于 TypeScript 的特性,管理依赖项非常容易,因为仅需按类型进行解析即可。

Scopes

Providers 通常具有与应用程序一致的生命周期。当应用程序启动时,必须处理解决每个依赖项,因此必须实例化每个 Provider;同样,当应用程序关闭时,每个 Provider 都将被销毁。

Custom providers

Nest 有一个内置的 IOC 容器(Nest运行时系统),可以解决 Providers 之间的依赖关系。此功能是上面描述的依赖注入功能的基础,但实际上要比我们到目前为止描述的功能强大得多。

@Injectable() 装饰器并不是定义一个 Provider 的唯一方式,我们还可以使用ValueClassFactory来定义一个 Provider。

1
2
3
4
5
6
7
8
@Module({
providers: [{
provide: APP_PIPE,
useClass: ValidationPipe
}]
})
export class PipeModule {
}

Property-based injection

除了基于构造函数注入 providers 的方式之外,在某些非常特殊的情况下,基于属性的注入可能会很有用。例如,如果父类依赖于一个或多个 providers,那么通过从构造函数中调用子类中的 super() 来传递它们就会非常烦人了。因此,为了避免出现这种情况,可以在属性上使用 @Inject() 装饰器。

Provider registration

现在我们已经定义了一个提供者(DictService),并且也已经有了该服务的使用者(DictController),此外,我们还需要将该服务注册到 Nest 中以便它可以进行注入。因此,我们需要编写模块文件(DictModule),并将该服务添加到 @Module() 装饰器的 providers 数组中:

1
2
3
4
5
6
7
@Module({
imports: [TypeOrmModule.forFeature([DictEntity])],
providers: [DictService],
controllers: [DictController],
})
export class DictModule {
}

在上面的示例中,当 Nest IOC 容器实例化 DictController 时,它首先查找所有的依赖项;当找到 DictService 依赖项时,它将对 DictService 令牌执行查找,根据 DictModule 的配置,将返回 DictService 类;然后,Nest 创建 DictService 实例,将其缓存并返回,或者如果实例已经在缓存中存在,则返回现有实例。

Modules

模块是用 @Module() 装饰器注释的类。Nest 使用模块来组织应用程序结构,@Module() 装饰器提供了 Nest 用来组织应用程序结构的元数据。

Metadata Describe
providers 由 Nest 注入器实例化的providers,并且至少可以在此模块中共享
controllers 在此模块中定义的需实例化的一组controllers
imports 导入模块的列表,这些模块道导出了此模块中所需的providers
exports 由本模块提供供其他模块导入使用的providers子集

默认情况下,模块封装提供者,这意味着如果提供者既不是当前模块的一部分也不是从另外模块(已导入)导出的,那么它是无法注入的。

Middleware

Middleware 是在路由处理程序之前执行的函数,该函数可以访问 requestresponse 对象,以及应用程序请求-响应周期中 next() 中间件函数。Nest 中间件实际上等价于 express 中间件。

中间件无法在这 @Module() 装饰器中进行配置,我们可以通过使用模块类的 configure() 方法来进行设置,且包含中间件的模块必须实现 NestModule 接口。

参考链接