先决条件
一分钟安装nodejs
,版本(>=10.13.0)
创建项目
1 | npm i -g @nestjs/cli |
初始项目结构
1 | # 将创建 project 目录, 安装node模块和一些其他样板文件,并将创建一个 src 目录,目录中包含几个核心文件。 |
1 | // main.ts |
平台
Nest 旨在成为一个与平台无关的框架。 通过平台,可以创建可重用的逻辑部件,开发人员可以利用这些部件来跨越多种不同类型的应用程序。 从技术 上讲,Nest 可以在创建适配器后使用任何 Node HTTP 框架。 有两个支持开箱即用的 HTTP 平台:express 和 fastify。 您可以选择最适合您 需求的产品。
平台 | 描述 |
---|---|
express | Express 是一个众所周知的 node.js 简约 Web 框架。 这是一个经过实战考验,适用于生产的库,拥有大量社区资源。 默认情况下使 |
用 @nestjs/platform-express
包。 许多用户都可以使用 Express ,并且无需采取任何操作即可启用它。|
|fastify|Fastify 是一个高性能,低开销的框架,专注于提供最高的效率和速度。 在这里阅读如何使用它。|
无论使用哪种平台,它都会暴露自己的 API。 它们分别是 NestExpressApplication 和 NestFastifyApplication。
将类型传递给 NestFactory.create() 函数时,如下例所示,app 对象将具有专用于该特定平台的函数。 但是,请注意,除非您确实要访问底层平 台API,否则无需指定类型。
1 | const app = await NestFactory.create<NestExpressApplication>(AppModule); |
运行应用程序
1 | npm run start |
控制器
控制器负责处理传入的 请求 和向客户端返回 响应 。
控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。
为了创建一个基本的控制器,我们使用类和装饰器。装饰器将类与所需的元数据相关联,并使 Nest 能够创建路由映射(将请求绑定到相应的控制器) 。
1 | // cats.controller.ts |
要使用 CLI 创建控制器,只需执行
nest g controller cats
命令。 为了在 express 中使用 Typescript (如 request: Request 上面的参数示例所示),请安装 @types/express 。
- 装饰器和普通表达对象的比较 |装饰器|普通表达对象| |:—|:—| |@Request()|req| |@Response() @Res()*|res| |@Next()|next| |@Session()|req.session| |@Param(key?: string)|req.params / req.params[key]| |@Body(key?: string)|req.body / req.body[key]| |@Query(key?: string)|req.query / req.query[key]| |@Headers(name?: string)|req.headers / req.headers[name]| |@Ip()|req.ip|
Http请求方法
- @Get()
- @Post()
- @Put()
- @Options()
- @Patch()
- @Delete()
- @Head()
- @All()
通配符
1 | 'ab*cd') ( |
状态码/响应头
1 | import { HttpCode, Header } from '@nestjs/common'; |
重定向
1 | () |
路由参数
1 | ':id') ( |
子域路由
1 | 'admin.example.com' }) ({ host: |
因为 Fastify 缺乏对嵌套路由器的支持,当使用子域路由时,应该使用(默认) Express 适配器。
1 | ':account.example.com' }) ({ host: |
Async/await
1 | () |
请求负载
- DTO(数据传输对象)模式
1 | // create-cat.dto.ts |
注册controller
控制器已经准备就绪,可以使用,但是 Nest 不知道 CatsController 是否存在,所以它不会创建这个类的一个实例。
控制器总是属于模块,这就是为什么我们将 controllers 数组保存在 @module() 装饰器中。
1
2
3
4
5
6
7
8// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
({
controllers: [CatsController],
})
export class AppModule {}
提供者
Providers 是 Nest 的一个基本概念。许多基本的 Nest 类可能被视为 provider - service, repository, factory, helper 等 等。 他们都可以通过 constructor 注入依赖关系。 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest运行时系统。 Provider 只是一个用 @Injectable() 装饰器注释的类。
服务
1 | // interfaces/cat.interface.ts |
要使用 CLI 创建服务类,只需执行
nest g service cats
命令。
controller中注入使用
1 | import { Controller, Get, Post, Body } from '@nestjs/common'; |
基于属性的注入
1 | import { Injectable, Inject } from '@nestjs/common'; |
注册提供者/服务
1 | import { Module } from '@nestjs/common'; |
目录结构
1 | src |
模块
模块是具有 @Module() 装饰器的类。 @Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。
@module() 装饰器接受一个描述模块属性的对象:
- providers 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享
- controllers 必须创建的一组控制器
- imports 导入模块的列表,这些模块导出了此模块中所需提供者
- exports 由本模块提供并应在其他模块中可用的提供者的子集。
细分功能模块
1 | // cats/cats.module.ts |
要使用 CLI 创建模块,只需执行
nest g module cats
命令。
- 导入到根模块
1
2
3
4
5
6
7import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
({
imports: [CatsModule],
})
export class ApplicationModule {}
目录结构
1 | src |
共享模块
在 Nest 中,默认情况下,模块是单例,因此您可以轻松地在多个模块之间共享同一个提供者实例。
1 | // cats.module.ts |
现在,每个导入 CatsModule 的模块都可以访问 CatsService ,并且它们将共享相同的 CatsService 实例。
模块导出
1 | ({ |
依赖注入
1 | import { Module } from '@nestjs/common'; |
但是,由于循环依赖性,模块类不能注入到提供者中。
全局模块
1 | import { Module, Global } from '@nestjs/common'; |
@Global 装饰器使模块成为全局作用域。 全局模块应该只注册一次,最好由根或核心模块注册。 在上面的例子中,CatsService 组件将无处不在, 而想要使用 CatsService 的模块则不需要在 imports 数组中导入 CatsModule。
使一切全局化并不是一个好的解决方案。 全局模块可用于减少必要模板文件的数量。 imports 数组仍然是使模块 API 透明的最佳方式。
动态模块
1 | import { Module, DynamicModule } from '@nestjs/common'; |
1 | import { Module } from '@nestjs/common'; |
中间件
中间件是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的
next()
中间件函数。next() 中间件函数通常由名为next的变量表示。
Nest 中间件实际上等价于 express 中间件。
- 中间件函数可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件函数。
- 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。
实现一个简单的日志中间件
1 | import { Injectable, NestMiddleware } from '@nestjs/common'; |
依赖注入
Nest中间件完全支持依赖注入。 就像提供者和控制器一样,它们能够注入属于同一模块的依赖项(通过 constructor )。
应用中间件
中间件不能在 @Module() 装饰器中列出。我们必须使用模块类的 configure() 方法来设置它们。包含中间件的模块必须实现 NestModule 接 口。我们将 LoggerMiddleware 设置在 ApplicationModule 层上。
1 | // app.module.ts |
函数式中间件
我们使用的 LoggerMiddleware 类非常简单。它没有成员,没有额外的方法,没有依赖关系。为什么我们不能只使用一个简单的函数?
中间件没有任何依赖关系时,我们可以考虑使用函数式中间件。
1 | // logger.middleware.ts改成 |
多个中间件
1 | consumer.apply(cors(), helmet(), logger).forRoutes(CatsController); |
全局中间件
1 | // INestApplication实例提供的 use()方法 |
异常过滤器
内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。
此操作由内置的全局异常过滤器执行,该过滤器处理类型 HttpException(及其子类)的异常。每个发生的异常都由全局异常过滤器处理, 当这个异 常无法被识别时 (既不是 HttpException 也不是继承的类 HttpException ) , 用户将收到以下 JSON 响应
1 | { |
基础异常类 HttpException
1 | () |
自定义异常
1 | export class ForbiddenException extends HttpException { |
内置HTTP异常
Nest 提供了一系列继承自核心异常 HttpException 的可用异常。
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
- ConflictException
- GoneException
- PayloadTooLargeException
- UnsupportedMediaTypeException
- UnprocessableException
- InternalServerErrorException
- NotImplementedException
- BadGatewayException
- ServiceUnavailableException
- GatewayTimeoutException
异常过滤器
1 | // http-exception.filter.ts |
所有异常过滤器都应该实现通用的 ExceptionFilter
接口。它需要你使用有效签名提供 catch(exception: T, host: ArgumentsHost)方法。T 表示异常的类型。
绑定过滤器
1 | // cats.controller.ts |
尽可能使用类而不是实例。由于 Nest 可以轻松地在整个模块中重复使用同一类的实例,因此可以减少内存使用。
全局过滤器
1 | async function bootstrap() { |
1 | // 模块设置过滤器 |
捕获异常
1 | // any-exception.filter.ts |
管道
管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。
管道有两个类型:
- 转换:管道将输入数据转换为所需的数据输出
- 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;
内置管道
- ValidationPipe
- ParseIntPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- DefaultValuePipe
1 | // ValidationPipe |
1 | export interface ArgumentMetadata { |
验证管道
1 | import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; |
绑定管道 UsePipes
1 | () |
类验证修饰符
1 | import { IsString, IsInt } from 'class-validator'; |
守卫
守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。
守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。 这通常称为 授权。
- 守卫在中间件之后执行,但是在任何拦截器和管道之前执行。
授权守卫
授权是保护的一个很好的用例,因为只有当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。我们现在要构建的 AuthGuard 假设用户是经过身份验证的(因此,请求头附加了一个token)。它将提取和验证token,并使用提取的信息来确定请求是否可以继续。
1 | // auth.guard.ts |
角色守卫
这个守卫只允许具有特定角色的用户访问。
绑定守卫
1 | // 同理 装饰器 全局绑定 模块绑定 |
反射器
1 | // 设置元数据 |
拦截器
拦截器是使用 @Injectable()
装饰器注解的类。拦截器应该实现 NestInterceptor
接口。
功能有:
- 在函数执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数 (例如, 缓存目的)
概念
每个拦截器都有 intercept() 方法,它接收2个参数。
第一个是 ExecutionContext 实例(与守卫完全相同的对象)。 ExecutionContext 继 承自 ArgumentsHost 。 ArgumentsHost 是传递给原始处理程序的参数的一个包装 ,它根据应用程序的类型包含不同的参数数组。
第二个参数是 CallHandler。如果不手动调用 handle() 方法,则主处理程序根本不会进行求值。这是什么意思?基本上,CallHandler是一个 包装执行流的对象,因此推迟了最终的处理程序执行。
截取切面
1 | // logging.interceptor.ts |
绑定拦截器
1 | // 装饰器 |
自定义装饰器
1 | // user.decorator.ts |
使用管道
1 | () |
多个装饰器
1 | import { applyDecorators } from '@nestjs/common'; |
结束🔚,有时间再介绍下GraphQL
。