likes
comments
collection
share

NestJS 基本理论(上)

作者站长头像
站长
· 阅读数 19

概述

NodeJS 基于 Chrome V8 引擎 表现优秀,同时社区发展速度飞快。

所以本文结合 NodeJS的优势和特点,调研主流框架的特点和实现服务的过程,认为 Nest 是那个最优秀的框架,无论是设计模式、抽象、工程化、社区生态...等多方面都非常出色。

介绍

Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。

它使用渐进式 JavaScript,内置并完全支持 TypeScript(但仍然允许开发人员使用纯 JavaScript 编写代码)并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素

在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。

平台

  • 目前支持两个 HTTP 平台,可以根据您的需求选择最适合平台。

    • express

    • fastify

  • 无论使用那个平台,都会将平台的 application 接口暴露出来。它们分别是

    • NestExpressApplication

    • NestFastifyApplication

  • platform-express

    Express 是一个著名的、极简的、专为 node 开发的 web 框架。它久经考验、适用于生产环境的软件库,并且拥有大量的社区资源。默认情况下使用 @nestjs/platform-express 软件包。许多用户对 Express 都很满意,并且无需采取任何操作即可启用它

  • platform-fastify

    Fastify 是一个高性能且低开销的框架,高度专注于提供最高的效率和速度

环境

  • Node 16.18.1
  • Npm 8.19.2

安装

  • 使用 CLI 安装
    $ npm i -g @nestjs/cli
    $ nest new project-name
    
  • 使用 Git 安装
    • TS 基础库
    $ git clone https://github.com/nestjs/typescript-starter.git project
    $ cd project
    $ npm install
    $ npm run start
    
    • 要使用 JS ,请在 git 安装替换为 javascript-starter.git
  • 手动创建
    $ npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata
    

VS Code 扩展

  • Nest-Server-Tools 帮助 nest-server 开发者在 vscode 中快速创建模板/目录/文件
  • NestJS Files 右键快速创建NestJS文件的代码扩展
  • NestJs Snippets 代码片段
  • Database Client 数据库客户端可视化
  • ESLint
  • Prettier ESLint
  • Prettier - Code formatter

下载依赖

$ npm install

运行服务

如果项目中使用了 mysql & redis,需要确保它们的服务被优先运行

# development
$ npm run start

# watch mode
$ npm run start:dev

# production mode
$ npm run start:prod

核心文件

  • app.controller.ts 带有单个路由的基本控制器
  • app.controller.spec.ts 针对控制器的单元测试
  • app.module.ts 应用程序的根模块(root module)
  • app.service.ts 具有单一方法的基本服务(service)
  • main.ts 应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。

核心概念

因涉及内容较多,此处只是简要介绍梳理,想阅读更多请查阅官网

控制器 Controller

控制器负责处理传入的请求和向客户端返回响应

NestJS 基本理论(上) 控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。

为了创建一个基本的控制器,我们使用类和装饰器。装饰器将类与所需的元数据相关联,并使 Nest 能够创建路由映射(将请求绑定到相应的控制器)。

如果在方法参数中定义了 @Res() 或 @Next(),此时该方法的 return 语句会被阻塞。此时必须使用 res.send / res.end / res.json 等

  • @Get()、@Post()、@Put()、@Patch()、@Delete()、@Options()、@Head()、@All() Controller 控制器下的 请求方式
  • @Request() req
  • @Response() res
  • @Next() next
  • @Session() req.session
  • @Param(param?: string) req.params / req.params[param]
  • @Body(param?: string) req.body / req.body[param]
  • @Query(param?: string) req.query / req.query[param]
  • @Headers(param?: string) req.headers / req.headers[param]
  • @HttpCode(200) 改变 http 状态码,但是在实际项目终于一般是会有统一的响应处理器来处理 http 响应
  • @Header('Cache-Control', 'none') 给我们的响应设置响应头,同样我们也可以直接使用@Res装饰器拿到响应的响应头

提供者 Provider

Providers 是 Nest 的一个基本概念。许多基本的 Nest 类都可能被视为 provider - service, repositoryfactoryhelper 等等。 他们都可以通过 constructor 注入依赖关系。 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest运行时系统。 Provider 只是一个用 @Injectable() 装饰器注释的类

NestJS 基本理论(上)

模块 Module

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

NestJS 基本理论(上) 每个 Nest 应用程序至少有一个模块,即根模块。根模块是 Nest 开始安排应用程序树的地方。事实上,根模块可能是应用程序中唯一的模块,特别是当应用程序很小时,但是对于大型程序来说这是没有意义的。在大多数情况下,您将拥有多个模块,每个模块都有一组紧密相关的功能

@module() 装饰器接受一个描述模块属性的对象:

providers由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享
controllers必须创建的一组控制器
imports导入模块的列表,这些模块导出了此模块中所需提供者
exports由本模块提供并应在其他模块中可用的提供者的子集。

默认情况下,该模块封装提供程序。这意味着无法注入既不是当前模块的直接组成部分,也不是从导入的模块导出的提供程序。因此,您可以将从模块导出的提供程序视为模块的公共接口或API。

xxx.mudule.ts 文件需要使用@Module() 装饰器的类,装饰器可以理解成一个封装好的函数

@Module() 参数理解:

  • providers服务提供者(处理具体的业务逻辑,各个模块之间可以共享)

  • controllers处理 http 请求,包括路由控制,向客户端返回响应(将具体业务逻辑委托给 providers 处理)

  • imports导入模块的列表(如果需要使用其他模块的服务,需要通过这里导入)

  • exports导出服务的列表(供其他模块导入使用。如果希望当前模块下的服务可以被其他模块共享,需要在这里配置导出)

中间件

中间件是在路由处理程序 之前 调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。 next() 中间件函数通常由名为 next 的变量表示。

NestJS 基本理论(上)

Nest 中间件实际上等价于 express 中间件。 下面是Express官方文档中所述的中间件功能:

中间件函数可以执行以下任务:

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件函数。
  • 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。

您可以在函数中或在具有 @Injectable() 装饰器的类中实现自定义 Nest中间件。 这个类应该实现 NestMiddleware 接口, 而函数没有任何特殊的要求。

异常过滤器

内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。

NestJS 基本理论(上) Nest提供了一个内置的 HttpException 类,它从 @nestjs/common 包中导入。对于典型的基于HTTP REST/GraphQL API的应用程序,最佳实践是在发生某些错误情况时发送标准HTTP响应对象。

HttpException 构造函数有两个必要的参数来决定响应:

  • response 参数定义 JSON 响应体。它可以是 string 或 object,如下所述。
  • status参数定义HTTP状态代码

默认情况下,JSON 响应主体包含两个属性:

  • statusCode:默认为 status 参数中提供的 HTTP 状态代码
  • message:基于状态的 HTTP 错误的简短描述

仅覆盖 JSON 响应主体的消息部分,请在 response参数中提供一个 string

要覆盖整个 JSON 响应主体,请在response 参数中传递一个object。 Nest将序列化对象,并将其作为JSON 响应返回。

第二个构造函数参数-status-是有效的 HTTP 状态代码。 最佳实践是使用从@nestjs/common导入的 HttpStatus枚举。

管道

管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口

NestJS 基本理论(上)

管道有两个典型的应用场景:

  • 转换:管道将输入数据转换为所需的数据输出(例如,将字符串转换为整数)
  • 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常

在这两种情况下, 管道 参数(arguments) 会由 控制器(controllers)的路由处理程序 进行处理。Nest 会在调用这个方法之前插入一个管道,管道会先拦截方法的调用参数,进行转换或是验证处理,然后用转换好或是验证好的参数调用原方法。

Nest自带很多开箱即用的内置管道。你还可以构建自定义管道。本章将介绍先内置管道以及如何将其绑定到路由处理程序(route handlers)上,然后查看一些自定义管道以展示如何从头开始构建自定义管道。

管道在异常区域内运行。这意味着当抛出异常时,它们由核心异常处理程序和应用于当前上下文的 异常过滤器 处理。当在 Pipe 中发生异常,controller 不会继续执行任何方法。这提供了用于在系统边界验证从外部源进入应用程序的数据的一种最佳实践。

内置管道

Nest 自带九个开箱即用的管道,即

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • DefaultValuePipe
  • ParseFilePipe

他们从 @nestjs/common 包中导出。

守卫

守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。

NestJS 基本理论(上)

守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。这通常称为授权。在传统的 Express 应用程序中,通常由中间件处理授权(以及认证)。中间件是身份验证的良好选择,因为诸如 token 验证或添加属性到 request 对象上与特定路由(及其元数据)没有强关联。

中间件不知道调用 next() 函数后会执行哪个处理程序。另一方面,守卫可以访问 ExecutionContext 实例,因此确切地知道接下来要执行什么。它们的设计与异常过滤器、管道和拦截器非常相似,目的是让您在请求/响应周期的正确位置插入处理逻辑,并以声明的方式进行插入。这有助于保持代码的简洁和声明性。

守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。

拦截器

拦截器是使用 @Injectable() 装饰器注解的类。拦截器应该实现 NestInterceptor 接口。

NestJS 基本理论(上)

拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:

  • 在函数执行之前/之后绑定额外的逻辑
  • 转换从函数返回的结果
  • 转换从函数抛出的异常
  • 扩展基本函数行为
  • 根据所选条件完全重写函数 (例如, 缓存目的)

自定义装饰器

Nest 是基于装饰器这种语言特性而创建的。在很多常见的编程语言中,装饰器是一个广为人知的概念,但在 JavaScript 世界中,这个概念仍然相对较新。所以为了更好地理解装饰器是如何工作的,你应该看看 这篇 文章。下面给出一个简单的定义:

ES2016 装饰器是一个表达式,它返回一个可以将目标、名称和属性描述符作为参数的函数。通过在装饰器前面添加一个 @ 字符并将其放置在你要装饰的内容的最顶部来应用它。可以为类、方法或属性定义装饰器。

TypeORM 实体监听装饰器

其实是 typeorm 在操作数据库时的生命周期,可以更方便的操作数据

  • 查找后:@AfterLoad
  • 插入前:@BeforeInsert
  • 插入后:@AfterInsert
  • 更新前:@BeforeUpdate
  • 更新后:@AfterUpdate
  • 删除前:@BeforeRemove

增删改查的三种方式

其实底层最终都会生成 sql 语句,只是封装了几种方式而已,方便人们使用

  • 第一种:使用 sql 语句,适用于 sql 语句熟练的同学

  • 第二种:typeorm 封装好的方法,增删改 + 简单查询

  • 第三种:QueryBuilder 查询生成器,适用于关系查询,多表查询,复杂查询