banner
banner
banner
NEWS LETTER

NestJs --- 可扩展Node.js服务器端应用的框架

Scroll down

平台:

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()不接受参数问题
  1. TS 先定义 DTO 对象,用于指定网络如何发送数据
  2. 使用类定义,因为接口在转译时会被删除,Nest 无法在运行时引用
  3. 定义完后再处理请求的方法中使用@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';
}

查询参数

  • 使用@Query()
  • 简单示例:
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}`;
}
  • 查询嵌套对象或数组
    • Express:
1
2
const app = (await NestFactory.create) < NestExpressApplication > AppModule
app.set('query parser', 'extended')
  • Fastify:
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

功能模块

  • 示例:
  1. 创建自定义模块,将同一文件下的控制器和服务注册到对应的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 {}
  2. 将创建后的模块注册到根模块上
    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] // 这样任何导入该模块的都可以访问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); // 动态模块通过options 和 entities 传递给providers
    return {
    global: true, // 这一行表示该注册的动态模块是全局范围,默认为false
    module: DatabaseModule,
    providers: providers,
    exports: providers,
    }; // 这个会覆盖@Module()
    }
    }
  • 引入动态模块
  • 示例
    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():可以传递1n个字符串,或一个路由信息对象,或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();
    };

    // 应用在AppModule
    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); // logger是个定义好的中间件
    await app.listen(process.env.PORT ?? 3000);

异常过滤器

其他文章
cover
Next --- React 开发框架
  • 26/02/22
  • 16:31
  • 前端工程化工具
cover
Mobx库
  • 26/02/22
  • 16:31
  • 前端工程化工具
目录导航 置顶
  1. 1. 平台:
  2. 2. 格式校验
  • 控制器
    1. 1. 基本写法:
    2. 2. 路由
    3. 3. 请求对象
    4. 4. 资源
    5. 5. 路由通配符(*)
    6. 6. 状态码
    7. 7. 响应头
    8. 8. 重定向
    9. 9. 路由参数 :
    10. 10. 子域路由
    11. 11. 异步性
    12. 12. 请求有效负载
    13. 13. 查询参数
    14. 14. 启动和运行
  • 供应商
    1. 1. 依赖注入
    2. 2. 范围
    3. 3. 自定义提供程序
    4. 4. 可选提供程序
    5. 5. 基于属性的注入
    6. 6. 提供商注入
  • 模块
    1. 1. 功能模块
    2. 2. 共享模块
    3. 3. 模块重新导出
    4. 4. 依赖关系注入
    5. 5. 全局模块
    6. 6. 动态模块
  • 中间件
    1. 1. 路由通配符
    2. 2. 中间件使用者
    3. 3. 排除路由
    4. 4. 函数式中间件
    5. 5. 多个中间件
    6. 6. 全局中间件
  • 异常过滤器
  • 请输入关键词进行搜索