About NestJS

Use `applyDecorators`, `ApiExtraModels`, `getSchemaPath` to implement customize swagger decorator, introduce swagger cli plugin. How to use ESM in Nest.JS? 如何解决 @nestjs/swagger 出现 schema 被覆盖的问题?

NestJS 自定义 Swagger 装饰器

https://docs.nestjs.com/openapi/operations#advanced-generic-apiresponse

比如统一泛型返回类型

typescript
1type ApiResponse<T> = {
2  timestamp: string;
3
4  path: string;
5
6  pathViewCount: number;
7
8  data: T;
9}

但是 Swagger ApiResponse 并不能使用泛型,只能使用 Class。
可以使用自定义如下注解实现类似效果

typescript
1import type { Type } from '@nestjs/common';
2import { applyDecorators } from '@nestjs/common';
3import { ApiExtraModels, ApiOkResponse, ApiResponseProperty, getSchemaPath } from '@nestjs/swagger';
4import type { ReferenceObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
5
6class ResponseInterceptorResult {
7  @ApiResponseProperty()
8  timestamp: string;
9
10  @ApiResponseProperty()
11  path: string;
12
13  @ApiResponseProperty()
14  pathViewCount: number;
15}
16
17export const ApiOkInterceptorResultResponse = <T extends Type>(options: {
18  model: T;
19  description?: string;
20  isArray?: boolean;
21}) => {
22  const { model, description, isArray } = options;
23  const ref: ReferenceObject = { $ref: getSchemaPath(model) };
24  return applyDecorators(
25    ApiExtraModels(ResponseInterceptorResult),
26    ApiExtraModels(model),
27    ApiOkResponse({
28      description,
29      schema: {
30        allOf: [
31          {
32            $ref: getSchemaPath(ResponseInterceptorResult),
33          },
34          {
35            properties: {
36              data: isArray ? { type: 'array', items: ref } : ref,
37            },
38          },
39        ],
40      },
41    }),
42  );
43};

Swagger Cli Plugin

This plugin is opt-in. If you prefer, you can declare all decorators manually, or only specific decorators where you need them.

关于 NestJS 不支持 ESM 的一个解决办法

Until NestJS support ESM modules, you can use dynamic imports and the compilerOption.moduleResolution: Node16 option in your tsconfig so that you can const module = await import('esm-pkg')

如何解决 @nestjs/swagger 出现重名 class 导致 schema 被覆盖的问题?

比如有两个 User 重名,使用 extends 取别名修改 class name 来避免被覆盖。

ts
1export class GithubUser extends User {
2  // ...
3}

关于allOfanyOfoneOf关键字的示例

以下是一些更详细的关于allOfanyOfoneOf关键字的示例。

  1. allOf 示例:

    假设我们有一个基础用户模型(BaseUser),包含idemail属性,另外还有一个名为AdminUser的扩展模型,添加了role属性。

    yaml
    1components:
    2  schemas:
    3    BaseUser:
    4      type: object
    5      properties:
    6        id:
    7          type: integer
    8        email:
    9          type: string
    10    AdminUser:
    11      allOf:
    12        - $ref: '#/components/schemas/BaseUser'
    13        - type: object
    14          properties:
    15            role:
    16              type: string

    在这个示例中,AdminUser模型继承自BaseUser模型,并添加了role属性。使用allOf关键字可以将BaseUser的属性与AdminUser的属性组合在一起。

  2. anyOf 示例:

    假设我们有两个不同类型的用户模型:InternalUserExternalUser,它们具有不同的属性。在某些场景下,我们可能需要一个API可以接受这两种类型的用户之一。

    yaml
    1components:
    2  schemas:
    3    InternalUser:
    4      type: object
    5      properties:
    6        id:
    7          type: integer
    8        username:
    9          type: string
    10    ExternalUser:
    11      type: object
    12      properties:
    13        externalId:
    14          type: integer
    15        emailAddress:
    16          type: string
    17    MixedUser:
    18      anyOf:
    19        - $ref: '#/components/schemas/InternalUser'
    20        - $ref: '#/components/schemas/ExternalUser'

    在这个示例中,我们创建了一个名为MixedUser的模型,它可以是InternalUserExternalUser。使用anyOf关键字表示该模型需要满足这两个模型之一的属性。

  3. oneOf 示例:

    假设我们有两种不同类型的支付方式:信用卡(CreditCard)和支付宝(Alipay)。我们希望一个API接受的支付方式是这两种中的一种,但不能同时为两者。

    yaml
    1components:
    2  schemas:
    3    CreditCard:
    4      type: object
    5      properties:
    6        cardNumber:
    7          type: string
    8        expiryDate:
    9          type: string
    10        cvv:
    11          type: integer
    12    Alipay:
    13      type: object
    14      properties:
    15        account:
    16          type: string
    17    PaymentMethod:
    18      oneOf:
    19        - $ref: '#/components/schemas/CreditCard'
    20        - $ref: '#/components/schemas/Alipay'

    在这个示例中,我们创建了一个名为PaymentMethod的模型,它可以是CreditCardAlipay,但不能同时为两者。使用oneOf关键字表示该模型需要满足这两个模型中恰好一个的属性。

nest.js 搭配 jest 单元测试时 exit gracefully

https://docs.nestjs.com/fundamentals/lifecycle-events

使用 onModuleDestroy 来优雅关闭连接或者后台任务