Openapi

CLI 插件

了解如何使用 @nestjs/swagger CLI 插件来自动生成 OpenAPI 装饰器,减少样板代码,并支持注释内省、SWC 构建器和 Jest 集成。

CLI 插件

TypeScript 的元数据反射系统有几个限制,使得无法确定类由哪些属性组成,或识别给定属性是可选的还是必需的。然而,其中一些约束可以在编译时解决。Nest 提供了一个插件,增强 TypeScript 编译过程,以减少所需的样板代码量。

info 提示 此插件是可选的。如果您愿意,可以手动声明所有装饰器,或仅在需要的地方声明特定装饰器。

概述

Swagger 插件将自动:

  • 使用 @ApiProperty 注释所有 DTO 属性,除非使用了 @ApiHideProperty
  • 根据问号设置 required 属性(例如 name?: string 将设置 required: false
  • 根据类型设置 typeenum 属性(也支持数组)
  • 根据分配的默认值设置 default 属性
  • 基于 class-validator 装饰器设置几个验证规则(如果 classValidatorShim 设置为 true
  • 为每个端点添加具有适当状态和 type(响应模型)的响应装饰器
  • 基于注释生成属性和端点的描述(如果 introspectComments 设置为 true
  • 基于注释生成属性的示例值(如果 introspectComments 设置为 true

请注意,您的文件名必须具有以下后缀之一:['.dto.ts', '.entity.ts'](例如,create-user.dto.ts),以便插件进行分析。

如果您使用不同的后缀,可以通过指定 dtoFileNameSuffix 选项来调整插件的行为(见下文)。

以前,如果您想为 Swagger UI 提供交互式体验,您必须复制大量代码来让包知道您的模型/组件应该如何在规范中声明。例如,您可以定义一个简单的 CreateUserDto 类如下:

export class CreateUserDto {
  @ApiProperty()
  email: string;

  @ApiProperty()
  password: string;

  @ApiProperty({ enum: RoleEnum, default: [], isArray: true })
  roles: RoleEnum[] = [];

  @ApiProperty({ required: false, default: true })
  isEnabled?: boolean = true;
}

虽然对于中等规模的项目来说这不是一个重大问题,但一旦您有大量类,它就会变得冗长且难以维护。

通过启用 Swagger 插件,上述类定义可以简单地声明为:

export class CreateUserDto {
  email: string;
  password: string;
  roles: RoleEnum[] = [];
  isEnabled?: boolean = true;
}

info 注意 Swagger 插件将从 TypeScript 类型和 class-validator 装饰器中派生 @ApiProperty() 注释。这有助于为生成的 Swagger UI 文档清楚地描述您的 API。但是,运行时的验证仍将由 class-validator 装饰器处理。因此,需要继续使用像 IsEmail()IsNumber() 等验证器。

因此,如果您打算依赖自动注释来生成文档并仍希望进行运行时验证,那么 class-validator 装饰器仍然是必需的。

info 提示 在 DTOs 中使用映射类型工具(如 PartialType)时,请从 @nestjs/swagger 而不是 @nestjs/mapped-types 导入它们,以便插件能够获取模式。

插件基于抽象语法树动态添加适当的装饰器。因此,您不必为散布在代码中的 @ApiProperty 装饰器而烦恼。

info 提示 插件将自动生成任何缺失的 swagger 属性,但如果您需要覆盖它们,只需通过 @ApiProperty() 显式设置它们。

注释内省

启用注释内省功能后,CLI 插件将基于注释生成属性的描述和示例值。

例如,给定一个示例 roles 属性:

/**
 * A list of user's roles
 * @example ['admin']
 */
@ApiProperty({
  description: `A list of user's roles`,
  example: ['admin'],
})
roles: RoleEnum[] = [];

您必须重复描述和示例值。启用 introspectComments 后,CLI 插件可以提取这些注释并自动为属性提供描述(和示例,如果定义的话)。现在,上述属性可以简单地声明如下:

/**
 * A list of user's roles
 * @example ['admin']
 */
roles: RoleEnum[] = [];

dtoKeyOfCommentcontrollerKeyOfComment 插件选项可用于自定义插件如何将值分配给 ApiPropertyApiOperation 装饰器。请参见下面的示例:

export class SomeController {
  /**
   * Create some resource
   */
  @Post()
  create() {}
}

这等同于以下指令:

@ApiOperation({ summary: "Create some resource" })

info 提示 对于模型,相同的逻辑适用,但与 ApiProperty 装饰器一起使用。

对于控制器,您不仅可以提供摘要,还可以提供描述(备注)、标签(如 @deprecated)和响应示例,如下所示:

/**
 * Create a new cat
 *
 * @remarks This operation allows you to create a new cat.
 *
 * @deprecated
 * @throws {500} Something went wrong.
 * @throws {400} Bad Request.
 */
@Post()
async create(): Promise<Cat> {}

使用 CLI 插件

要启用插件,请打开 nest-cli.json(如果您使用 Nest CLI)并添加以下 plugins 配置:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/swagger"]
  }
}

您可以使用 options 属性来自定义插件的行为。

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/swagger",
        "options": {
          "classValidatorShim": false,
          "introspectComments": true,
          "skipAutoHttpCode": true
        }
      }
    ]
  }
}

options 属性必须满足以下接口:

export interface PluginOptions {
  dtoFileNameSuffix?: string[];
  controllerFileNameSuffix?: string[];
  classValidatorShim?: boolean;
  dtoKeyOfComment?: string;
  controllerKeyOfComment?: string;
  introspectComments?: boolean;
  skipAutoHttpCode?: boolean;
  esmCompatible?: boolean;
}
选项默认值描述
dtoFileNameSuffix['.dto.ts', '.entity.ts']DTO(数据传输对象)文件后缀
controllerFileNameSuffix.controller.ts控制器文件后缀
classValidatorShimtrue如果设置为 true,模块将重用 class-validator 验证装饰器(例如 @Max(10) 将在模式定义中添加 max: 10
dtoKeyOfComment'description'ApiProperty 上设置注释文本的属性键
controllerKeyOfComment'summary'ApiOperation 上设置注释文本的属性键
introspectCommentsfalse如果设置为 true,插件将基于注释生成属性的描述和示例值
skipAutoHttpCodefalse禁用在控制器中自动添加 @HttpCode()
esmCompatiblefalse如果设置为 true,解决使用 ESM({ "type": "module" })时遇到的语法错误

确保在更新插件选项时删除 /dist 文件夹并重新构建应用程序。 如果您不使用 CLI 而是有自定义的 webpack 配置,可以将此插件与 ts-loader 结合使用:

getCustomTransformers: (program: any) => ({
  before: [require('@nestjs/swagger/plugin').before({}, program)]
}),

SWC 构建器

对于标准设置(非 monorepo),要在 SWC 构建器中使用 CLI 插件,您需要启用类型检查,如此处所述。

$ nest start -b swc --type-check

对于 monorepo 设置,请按照此处的说明操作。

$ npx ts-node src/generate-metadata.ts
# OR npx ts-node apps/{YOUR_APP}/src/generate-metadata.ts

现在,序列化的元数据文件必须由 SwaggerModule#loadPluginMetadata 方法加载,如下所示:

import metadata from './metadata'; // <-- 由 "PluginMetadataGenerator" 自动生成的文件

await SwaggerModule.loadPluginMetadata(metadata); // <-- 这里
const document = SwaggerModule.createDocument(app, config);

ts-jest 集成(e2e 测试)

要运行 e2e 测试,ts-jest 会在内存中即时编译您的源代码文件。这意味着它不使用 Nest CLI 编译器,也不应用任何插件或执行 AST 转换。

要启用插件,请在您的 e2e 测试目录中创建以下文件:

const transformer = require('@nestjs/swagger/plugin');

module.exports.name = 'nestjs-swagger-transformer';
// 每当您更改下面的配置时,都应该更改版本号 - 否则,jest 不会检测到更改
module.exports.version = 1;

module.exports.factory = (cs) => {
  return transformer.before(
    {
      // @nestjs/swagger/plugin 选项(可以为空)
    },
    cs.program, // 对于较旧版本的 Jest (<= v27),使用 "cs.tsCompiler.program"
  );
};

有了这个,在您的 jest 配置文件中导入 AST 转换器。默认情况下(在启动应用程序中),e2e 测试配置文件位于 test 文件夹下,名为 jest-e2e.json

如果您使用 jest@<29,则使用下面的代码片段。

{
  ... // 其他配置
  "globals": {
    "ts-jest": {
      "astTransformers": {
        "before": ["<上面创建的文件的路径>"]
      }
    }
  }
}

如果您使用 jest@^29,则使用下面的代码片段,因为之前的方法已被弃用。

{
  ... // 其他配置
  "transform": {
    "^.+\\.(t|j)s$": [
      "ts-jest",
      {
        "astTransformers": {
          "before": ["<上面创建的文件的路径>"]
        }
      }
    ]
  }
}

故障排除 jest(e2e 测试)

如果 jest 似乎没有获取您的配置更改,可能是 Jest 已经缓存了构建结果。要应用新配置,您需要清除 Jest 的缓存目录。

要清除缓存目录,请在您的 NestJS 项目文件夹中运行以下命令:

$ npx jest --clearCache

如果自动缓存清除失败,您仍然可以使用以下命令手动删除缓存文件夹:

# 查找 jest 缓存目录(通常是 /tmp/jest_rs)
# 通过在您的 NestJS 项目根目录中运行以下命令
$ npx jest --showConfig | grep cache
# 示例结果:
#   "cache": true,
#   "cacheDirectory": "/tmp/jest_rs"

# 删除或清空 Jest 缓存目录
$ rm -rf  <cacheDirectory value>
# 例如:
# rm -rf /tmp/jest_rs