平台:
Nest 旨在成为一个与平台无关的框架。能够与任何 Node HTTP 框架一起使用。支持两种开箱即用的 HTTP 平台:express 和 fastify。
- platform-express:
- Express 是 node 的知名极简 Web 框架。还有大量社区资源。默认使用@nestjs/platform-express。
- 对应接口:NestExpressApplication
- platform-fastify:
- Fastify 是一个高性能和低开销的框架,高度专注于提供最大的效率和速度。
- 对应接口:NestFastifyApplication
- 使用方式:
const app = await NestFactory.create<NestExpressApplication>(AppModule);
格式校验
- 生成 Nest 项目同时预装了代码得 linter 和格式化程序(eslint 和 prettier)
- 检查
npm run lint
npm run format
控制器
负责处理传入请求并响应发送回客户端。一个控制器有多个路由,每个路由可以执行不同得操作
- 一个基本的控制器,使用 classes 和 decorators。装饰器将类和必要得元数据连接起来,允许 Nest 创建一个路由映射
基本写法:
1 2 3 4 5 6 7 8 9 10 11
| @@Controller() export class XxxController { @Post('cats') create(): string { return 'This action adds a new cat'; } @Get() anyMethods(): string { return '请求路由到这个自定义方法,方法名任意但一定要有,返回对应的选项来响应请求' } }
|
路由
- 使用@Controller() 装饰器指定一个[可选]路由路径前缀,可以对路由关联分组,减少重复代码
- 使用命令行创建控制器:
nest g controller [name]
- 路由路径前缀:
/路由路径前缀
- 示例:@Controller(‘cats’) 指向 /cats
- @Get(): 定义这个断点的请求路由路径,如果有参数,则路径是跟着@Controller 的前缀路径往下写。
- 示例:@Get(‘breed’),则生成的路由将是 GET /cats/breed
- anyMethods():用来响应请求,并返回状态码。响应的两种选项:
- Standard (recommended):
- 返回一个 JavaScript『对象或数组』时,会被自动被『序列化』为 JSON。
- 返回一个 JavaScript『原始类型』(String、Number、Boolean)时,Nest 仅发送值而『不序列化』
- Library-specific:使用特定库响应对象,该对象可以通过『在方法处理程序中使用@Res()装饰器注入』。这种方法可以使用该对象提供的原生响应处理方法。
- 如果使用@Res() 或 @Next()时默认选择 Library-specific。如果同时使用这两种方法,Standard 将会禁用单一路由。同时使用必须设置@Res({ passthrough: true }),如仅设置 cookies/headers 的情况下使用。
请求对象
- 在处理程序的签名中使用@Req()装饰器来指示 Nest 注入请求对象。
- 示例:
anyMethods(@Req() request: Request): string {return '返回响应请求'}
- 提示:要利用 Express 类型,确保安装@types/express 包
- 请求对象表示 HTTP 请求,并包含查询字符串、参数、HTTP 头和主体的属性。使用@Body()或@Query()这类的装饰器,可以直接使用。
| decorators |
results |
| @Request(), @Req() |
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 |
| @HostParam() |
req.hosts |
资源
- 提供了所有标准 HTTP 方式:
- @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @Head()
- @All()可以处理所有时间
路由通配符(*)
- 用于匹配路径末尾的路由中任意字符组合。
- 示例: @Get(‘a/*‘) 匹配 a/23, a/,a/abc 等
- 在纯粹的 Express 中,必须使用命名的通配符才能让路由工作,例如,abcd/*splat,其中 splat 只是通配符参数的名称,并没有特殊含义,可以任意命名。
- 对于在路由中间使用*,Express 要求使用命名通配符(ab{*splat}ad), 而 Fastify 却不支持
状态码
- get 默认返回 200, post 默认返回 201
- 可以使用@HttpCode()装饰器来更改默认返回的状态码
- 要从@nestjs/common 包中引入
- 也可以使用 Library-specific 的@Res()注入
- 示例:
1 2 3 4 5
| @Post() @HttpCode(204) create() { return 'This action adds a new cat'; }
|
响应头
- 使用@Header()装饰器 或者 Library-specific 的@Headers(name?: string)
- 要从@nestjs/common 包中引入
- 示例:
1 2 3 4 5
| @Post() @Header('Cache-Control', 'no-store') create() { return 'This action adds a new cat'; }
|
重定向
- 使用@Redirect(url, statusCode(默认不写为 302,可指定))
- 动态 url 或者状态码,通过返回一个遵循 HttpRedirectResponse 接口。返回的值将覆盖@Redirect 的任何参数
- 示例:
1 2 3 4 5 6 7
| @Get() @Redirect('https://nestjs.com', 301) getDocs(@Query('version') version) { if (version && version === '5') { return { url: 'https://docs.nestjs.com/v5/' }; } }
|
路由参数 :
- 动态请求时使用, 在@Get()装饰器的路由路径中添加路由参数令牌
- 然后通过在处理请求的函数参数中使用@Param()得到不同的路径
- @Param()从@nestjs/common 中引入
- 示例:
1 2 3 4 5 6 7 8 9 10 11
| 写法一: 直接拿params对象 @Get(':id') findOne(@Param() params: any): string { return `This action return a #${params.id} cat` }
写法二:从params对象解构单个值 @Get(':id') findOne(@Param('id') id: string): string { return `This action return a #${id} cat` }
|
子域路由
- @Controller 装饰器可以要求传入请求的 HTTP 主机与某个特定值匹配, 这个特定值也可以动态获取
- 使用子域路由默认使用 Express 适配器
- 示例:
1 2 3 4 5 6 7
| @Controller({ host: ':account.example.com' }) export class AccountController { @Get() getInfo(@HostParam('account') account: string) { return account } }
|
异步性
1 2 3 4 5 6 7 8 9 10 11
| 写法一: @Get() async findAll(): Promise<any[]> { return [] }
写法二: @Get() findAll(): Observable<any[]> { return of([]) }
|
请求有效负载
- 使用@Body 装饰器解决@Post()不接受参数问题
- TS 先定义 DTO 对象,用于指定网络如何发送数据
- 使用类定义,因为接口在转译时会被删除,Nest 无法在运行时引用
- 定义完后再处理请求的方法中使用@Body 和添加参数
1 2 3 4 5 6 7 8 9 10
| export class CreateCatDto { name: string; age: number; breed: string; }
@Post() async create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; }
|
查询参数
1 2 3 4
| @Get() async findAll(@Query('age') age: number, @Query('breed') breed: string) { return `This action returns all cats filtered by age: ${age} and breed: ${breed}`; }
|
1 2
| const app = (await NestFactory.create) < NestExpressApplication > AppModule app.set('query parser', 'extended')
|
1 2 3 4 5 6
| const app = await NestFactory.create<NestFastifyApplication>( AppModule, new FastifyAdapter({ querystringParser: (str) => qs.parse(str), }), );
|
启动和运行
- 控制器必须始终是模块的一部分,所以要将控制器注册到 Module 中
- 示例:
1 2 3 4 5 6 7
| import { Module } from '@nestjs/common' import { CatsController } from './cats/cats.controller'
@Module({ controllers: [CatsController], }) export class AppModule {}
|
供应商
- @Injectable(): 将元数据附加到类,表明该类可由 Nest IoC 容器管理
依赖注入
- Nest 时围绕 Dependency Injection 的强大设计模式构建的
- 在 Nest 中,由于 TS,会根据其类型解析。如
constructor(private catsService: CatsService) {},Nest 将通过创建并返回 CatsService 的实例来解析 catsService。然后,这个依赖关系将被注入到控制器的构造函数中(或分配给指定的属性)
范围
- 当应用程序被引导时,必须解决每个依赖项,这意味着每个提供程序都会被实例化。
- 当应用程序关闭时,所有提供程序都将被销毁。
自定义提供程序
- Nest 带有一个内置的控制反转(“IoC”)容器,用于管理提供商之间的关系。此功能是依赖项注入的基础
- 定义提供程序的方法:
可选提供程序
- 有默认值的,且不总是需要解决。使用@Optional()将程序标记为可选
- 示例:
1 2 3 4 5 6
| import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable() export class HttpService<T> { constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {} }
|
基于属性的注入
- 当顶级类依赖于一个或多个提供程序时,使用@Inject()
- 示例:
1 2 3 4 5 6 7
| import { Injectable, Inject } from '@nestjs/common';
@Injectable() export class HttpService<T> { @Inject('HTTP_OPTIONS') private readonly httpClient: T; }
|
提供商注入
- 通过编辑 app.module.ts 将要注册的服务添加到@Module 对应的数组中
- 示例:
1 2 3 4 5 6 7 8 9
| import { Module } from '@nestjs/common' import { CatsController } from './cats/cats.controller' import { CatsService } from './cats/cats.service'
@Module({ controllers: [CatsController], providers: [CatsService], }) export class AppModule {}
|
模块
- 模块是用@Module()装饰器注释的类,此装饰器提供元数据
- 每个 Nest 应用程序都至少有一个模块,即根模块,它是 Nest 构建应用程序图的起点。
- @Module()接受一个具有描述模块的属性的单个对象:
|
|
| providers |
将由 Nest 注入器实例化的提供者,并且至少可以在此模块之间共享 |
| controllers |
此模块中定义的必须实例化的控制器集 |
| imports |
导入的模块列表,这些模块导出此模块中所需的提供程序 |
| exports |
此模块提供 providers 的子集,并且其他导入该模块的可用。可以使用提供程序本身,也可以只使用其provide value |
功能模块
- 创建自定义模块,将同一文件下的控制器和服务注册到对应的controllers和providers
1 2 3 4 5 6 7 8 9 10 11
| cats/cats.module.ts
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service';
@Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {}
|
- 将创建后的模块注册到根模块上
1 2 3 4 5 6 7 8 9
| app.module.ts
import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module';
@Module({ imports: [CatsModule], }) export class AppModule {}
|
共享模块
- 模块默认是单例的
- 每个模块都自动成为共享模块。创建后,任何模块都可以重复使用。
- 将要共享的实例,封装在模块并导出。该实例就可以重复使用
- 示例:
1 2 3 4 5 6 7 8 9 10
| import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service';
@Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService] }) export class CatsModule {}
|
模块重新导出
- 示例:
1 2 3 4 5
| @Module({ imports: [CommonModule], exports: [CommonModule], }) export class CoreModule {}
|
依赖关系注入
- 模块类可以注入providers
- 由于circular dependency,模块类本身不能作为providers
- 示例:
1 2 3 4 5 6 7 8 9 10 11
| import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service';
@Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule { constructor(private catsService: CatsService) {} }
|
全局模块
- 注册模块时使用@Global()装饰器
- 全局模块通常只能由 root 或 core 模块注册一次
- 示例:
1 2 3 4 5 6 7 8 9 10 11
| import { Module, Global } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service';
@Global() @Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService], }) export class CatsModule {}
|
动态模块
- 在运行时配置的模块
- forRoot() 可以同步或异步返回动态模块(即通过一个Promise)
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { Module, DynamicModule } from '@nestjs/common'; import { createDatabaseProviders } from './database.providers'; import { Connection } from './connection.provider';
@Module({ providers: [Connection], exports: [Connection], }) export class DatabaseModule { static forRoot(entities = [], options?): DynamicModule { const providers = createDatabaseProviders(options, entities); return { global: true, module: DatabaseModule, providers: providers, exports: providers, }; } }
|
- 引入动态模块
- 示例
1 2 3 4 5 6 7 8
| import { Module } from '@nestjs/common'; import { DatabaseModule } from './database/database.module'; import { User } from './users/entities/user.entity';
@Module({ imports: [DatabaseModule.forRoot([User])], }) export class AppModule {}
|
中间件
- 在路由处理程序之前调用的函数
- 默认情况下,Nest中间件 等同于 express中间件
- 作用:
- 执行任何代码
- 更改 request和response 对象
- 结束 request-response 循环
- 调用堆栈中的下一个中间件函数
- 如果当前的中间件函数没有结束 request-response 循环,必须调用next()将控制权传递给下一个中间件函数。否则请求将保持挂起状态。
- 应用
- 在AppModule中通过configure去设置
- 可以在配置中间件时通过forRoutes()配置路由路径和请求方式,并传递给方法
- 在configure()中可以使用async/await异步操作
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 }); } }
|
路由通配符
- splat: 匹配路由中的任意字符组合,splat可以随意命名,无特殊意义。 特殊的,连字符【-】和【.】后面没跟其他字符则不匹配。但是将splat放在{}中则会匹配
- 示例:
1 2 3 4
| forRoutes({ path: 'abcd/*splat', method: RequestMethod.ALL, });
|
中间件使用者
- 提供管理中间站的方法。这些方法都可以使用Fluent样式链接
- forRoutes():可以传递1
n个字符串,或一个路由信息对象,或1n个控制器。以逗号分隔
排除路由
- exclude(): 接受1~n个字符串或对象来标识要排除的路由
- 示例:
1 2 3 4 5 6 7 8
| consumer .apply(LoggerMiddleware) .exclude( { path: 'cats', method: RequestMethod.GET }, { path: 'cats', method: RequestMethod.POST }, 'cats/{*splat}', ) .forRoutes(CatsController);
|
函数式中间件
- 不需要依赖项时使用
- 示例:
1 2 3 4 5 6 7 8 9 10 11
| import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); };
consumer .apply(logger) .forRoutes(CatsController)
|
多个中间件
- apply(), 按顺序绑定多个中间件,并用逗号分割
- 示例:
1
| consumer.apply(cors(),helmet(),logger).forRoutes(CatsController)
|
全局中间件
- use():一次行将中间件绑定到每个已注册的路由
- 示例:
1 2 3
| const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(process.env.PORT ?? 3000);
|
异常过滤器