Skip to content

Latest commit

 

History

History
1302 lines (972 loc) · 33.3 KB

openapi.md

File metadata and controls

1302 lines (972 loc) · 33.3 KB

OPENAPI

介绍

OpenAPI是一个与语言无关的RESTful API定义说明,Nest提供了一个专有的模块来利用装饰器生成类似声明。

安装

要开始使用,首先安装依赖、

$ npm install --save @nestjs/swagger swagger-ui-express

如果使用fastify,安装fastify-swagger而不是swagger-ui-express:

$ npm install --save @nestjs/swagger fastify-swagger

引导

安装完成后,在main.ts文件中定义并初始化SwaggerModule类:

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

?> 文档(通过SwaggerModule#createDocument()方法返回)是一个遵循OpenAPI文档的序列化对象。除了HTTP,你也可以以JSON/YAML文件格式保存和使用它。

DocumentBuilder建立一个遵循OpenAPI 标准的基础文档。它提供了不同的方法来配置类似标题、描述、版本等信息属性。要创建一个完整的文档(使用HTTP定义),我们使用SwaggerModule类的createDocument()方法。这个方法有两个参数,一个应用实例和一个Swagger选项对象。我们也可以提供第三个SwaggerDocumentOptions类型可选对象,见文档选项

创建文档后,调用setup()方法,它接受:

  1. 挂载Swagger界面的路径。
  2. 应用实例。
  3. 上述实例化的文档对象。

运行以下命令启动HTTP服务器。

$ npm run start

浏览http://localhost:3000/api可以看到Swagger界面。

swagger1

Swagger模块自动反射你所有的终端。注意Swagger界面根据平台不同,由swagger-ui-expressfastify-swagger生成。

?> 要生成和下载一个Swagger JSON文件,导航到http://localhost:3000/api-json (swagger-ui-express) 或http://localhost:3000/api/json (fastify-swagger) (假设API文档在 http://localhost:3000/api路径)。

!> 在使用fastify-swaggerhelmet时可能有CSP问题,要处理这个冲突,参考如下配置CSP。

app.register(helmet, {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: [`'self'`],
      styleSrc: [`'self'`, `'unsafe-inline'`],
      imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
      scriptSrc: [`'self'`, `https: 'unsafe-inline'`],
    },
  },
});

// If you are not going to use CSP at all, you can use this: app.register(helmet, { contentSecurityPolicy: false, });

文档选项

创建文档时,可以提供一些额外选项来配合库特性。这些选项应该是SwaggerDocumentOptions类型:

export interface SwaggerDocumentOptions {
  /**
   * List of modules to include in the specification
   */
  include?: Function[];

  /**
   * Additional, extra models that should be inspected and included in the specification
   */
  extraModels?: Function[];

  /**
   * If `true`, swagger will ignore the global prefix set through `setGlobalPrefix()` method
   */
  ignoreGlobalPrefix?: boolean;

  /**
   * If `true`, swagger will also load routes from the modules imported by `include` modules
   */
  deepScanRoutes?: boolean;

  /**
   * Custom operationIdFactory that will be used to generate the `operationId`
   * based on the `controllerKey` and `methodKey`
   * @default () => controllerKey_methodKey
   */
  operationIdFactory?: (controllerKey: string, methodKey: string) => string;
}

例如,如果你要确保库像createUser而不是UserController_createUser一样生成操作名称,可以做如下配置:

const options: SwaggerDocumentOptions =  {
  operationIdFactory: (
    controllerKey: string,
    methodKey: string
  ) => methodKey
});
const document = SwaggerModule.createDocument(app, config, options);

示例

一个例子见这里

类型和参数

SwaggerModule在路径处理程序上搜索所有@Body(), @Query(), 以及@Param()装饰器来生成API文档。它也利用反射来创建响应模型。考虑以下代码:

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

?> 要显式定义主体,使用@ApiBody()装饰器 (从@nestjs/swagger引入).

基于CreateCatDto,将创建以下Swagger页面模型。

如你所见,虽然类已经声明了一些属性,但这里的定义是空的。要使这些类属性在SwaggerModule中可见,我们要么用@ApiProperty()装饰器或使用CLI插件来自动生成:

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty()
  name: string;

  @ApiProperty()
  age: number;

  @ApiProperty()
  breed: string;
}

?> 考虑使用Swagger插件(参见CLI插件)来自动生成以代替手动装饰每个属性。

打开浏览器确认生成的CreateCatDto模型:

@ApiProperty()装饰器也允许设置不同的原型对象属性:

@ApiProperty({
  description: 'The age of a cat',
  minimum: 1,
  default: 1,
})
age: number;

?> 可以使用@ApiPropertyOptional()速记装饰器来替代显式输入@ApiProperty({ required: false })

要显式指定属性类型,使用type字段:

@ApiProperty({
  type: Number,
})
age: number;

数组

当属性是数组时,我们必须手动指定数组类型:

@ApiProperty({ type: [String] })
names: string[];

考虑使用Swagger 插件来自动发现数组

要么将类型作为数组的第一个元素(如上),要么将isArray属性设为true

循环依赖

当你的类之间有循环依赖时,使用SwaggerModul提供的一个包含类型信息的懒函数。

@ApiProperty({ type: () => Node })
node: Node;

考虑使用Swagger 插件来自动发现循环依赖

泛型和接口

由于TypeScript没有存储泛型或者接口的元数据,当你在DTO中使用他们的时候,SwaggerModule可能不会正确生成运行时的模型定义。基于此,下列代码不会被Swagger模块正确识别。

createBulk(@Body() usersDto: CreateUserDto[])

要处理这些限制,需要显式配置类型:

@ApiBody({ type: [CreateUserDto] })
createBulk(@Body() usersDto: CreateUserDto[])

枚举

要定义一个枚举,需要在@ApiProperty中用数组手动设置enum属性。

@ApiProperty({ enum: ['Admin', 'Moderator', 'User']})
role: UserRole;

也可以如下定义一个真实的TypeScript泛型:

export enum UserRole {
  Admin = 'Admin',
  Moderator = 'Moderator',
  User = 'User',
}

可以在@Query()参数中配合@ApiQuery()装饰器直接使用enum

@ApiQuery({ name: 'role', enum: UserRole })
async filterByRole(@Query('role') role: UserRole = UserRole.User) {}

isArray配置为true时, enum可以多选。

枚举原型

默认地,enum属性将为Enumparameter上添加一个原始定义。

- breed:
    type: 'string'
    enum:
      - Persian
      - Tabby
      - Siamese

上述定义在大部分情况下工作良好。然而,如果你使用该定义作为输入在客户端生成代码时,可能会遇到属性包含重复枚举的情况,考虑以下代码:

// generated client-side code
export class CatDetail {
  breed: CatDetailEnum;
}

export class CatInformation {
  breed: CatInformationEnum;
}

export enum CatDetailEnum {
  Persian = 'Persian',
  Tabby = 'Tabby',
  Siamese = 'Siamese',
}

export enum CatInformationEnum {
  Persian = 'Persian',
  Tabby = 'Tabby',
  Siamese = 'Siamese',
}

?> 上述代码使用NSwag工具生成

现在可以看到有两个枚举完全一样,要处理这个问题,需要在装饰器的enum属性中传入enumName参数。

export class CatDetail {
  @ApiProperty({ enum: CatBreed, enumName: 'CatBreed' })
  breed: CatBreed;
}

enumName属性使能@nestjs/swagger来将CatBreed转换为其原型,从而使CatBreed可重用:

CatDetail:
  type: 'object'
  properties:
    ...
    - breed:
        schema:
          $ref: '#/components/schemas/CatBreed'
CatBreed:
  type: string
  enum:
    - Persian
    - Tabby
    - Siamese

?> 任何包含enum属性的装饰器都有enumName

原始定义

在一些特殊场合(例如深度嵌套的数组和矩阵),你可能需要手动描述你的类型。

@ApiProperty({
  type: 'array',
  items: {
    type: 'array',
    items: {
      type: 'number',
    },
  },
})
coords: number[][];

类似地,要在控制器类中手动定义输入输出,使用schema属性:

@ApiBody({
  schema: {
    type: 'array',
    items: {
      type: 'array',
      items: {
        type: 'number',
      },
    },
  },
})
async create(@Body() coords: number[][]) {}

额外模型

要定义控制器中没有直接使用,但是需要被Swagger模块检查的额外的模型,使用@ApiExtraModels()装饰器:

@ApiExtraModels(ExtraModel)
export class CreateCatDto {}

?> 只需要对指定的model类使用一次@ApiExtraModels()

你也可以把一个选项对象和extraModels属性一起传递给SwaggerModule#createDocument() 方法:

const document = SwaggerModule.createDocument(app, options, {
  extraModels: [ExtraModel],
});

要获得一个模型的引用($ref) ,使用getSchemaPath(ExtraModel)函数:

'application/vnd.api+json': {
   schema: { $ref: getSchemaPath(ExtraModel) },
},

oneOf, anyOf, allOf

要组合原型,你可以使用oneOf,anyOf 或者allOf关键词(阅读更多).

@ApiProperty({
  oneOf: [
    { $ref: getSchemaPath(Cat) },
    { $ref: getSchemaPath(Dog) },
  ],
})
pet: Cat | Dog;

如果你要定义一个多态数组(例如,数组成员跨越多个原型),你应该使用前节的原始定义来手动定义你的类型。

type Pet = Cat | Dog;

@ApiProperty({
  type: 'array',
  items: {
    oneOf: [
      { $ref: getSchemaPath(Cat) },
      { $ref: getSchemaPath(Dog) },
    ],
  },
})
pets: Pet[];

?> getSchemaPath()函数从@nestjs/swagger引入.

CatDog都应该使用@ApiExtraModels()装饰器 (在类水平).

操作

在OpenAPI规范中,API暴露的以{资源}为结束的终端,例如/users或者/reports/summary,都是可以执行HTTP方法的,例如GET,POST或者DELETE

标签

要为控制器附加一个标签,使用`@ApiTags(...tags)装饰器。

@ApiTags('cats')
@Controller('cats')
export class CatsController {}

报头

要作为请求的一部分定义自定义报头,使用@ApiHeader()装饰器。

@ApiHeader({
  name: 'X-MyHeader',
  description: 'Custom header',
})
@Controller('cats')
export class CatsController {}

响应

要定义一个自定义响应, 使用`@ApiResponse()装饰器.

@Post()
@ApiResponse({ status: 201, description: 'The record has been successfully created.'})
@ApiResponse({ status: 403, description: 'Forbidden.'})
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

Nest提供了一系列继承自@ApiResponse装饰器的用于速记的API响应装饰器:

  • @ApiOkResponse()
  • @ApiCreatedResponse()
  • @ApiAcceptedResponse()
  • @ApiNoContentResponse()
  • @ApiMovedPermanentlyResponse()
  • @ApiBadRequestResponse()
  • @ApiUnauthorizedResponse()
  • @ApiNotFoundResponse()
  • @ApiForbiddenResponse()
  • @ApiMethodNotAllowedResponse()
  • @ApiNotAcceptableResponse()
  • @ApiRequestTimeoutResponse()
  • @ApiConflictResponse()
  • @ApiTooManyRequestsResponse()
  • @ApiGoneResponse()
  • @ApiPayloadTooLargeResponse()
  • @ApiUnsupportedMediaTypeResponse()
  • @ApiUnprocessableEntityResponse()
  • @ApiInternalServerErrorResponse()
  • @ApiNotImplementedResponse()
  • @ApiBadGatewayResponse()
  • @ApiServiceUnavailableResponse()
  • @ApiGatewayTimeoutResponse()
  • @ApiDefaultResponse()
@Post()
@ApiCreatedResponse({ description: 'The record has been successfully created.'})
@ApiForbiddenResponse({ description: 'Forbidden.'})
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

要从请求返回一个指定的模型,需要创建一个类并用@ApiProperty()装饰器注释它。

export class Cat {
  @ApiProperty()
  id: number;

  @ApiProperty()
  name: string;

  @ApiProperty()
  age: number;

  @ApiProperty()
  breed: string;
}

Cat模型可以与响应装饰器的type属性组合使用。

@ApiTags('cats')
@Controller('cats')
export class CatsController {
  @Post()
  @ApiCreatedResponse({
    description: 'The record has been successfully created.',
    type: Cat,
  })
  async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
    return this.catsService.create(createCatDto);
  }
}

打开浏览器确认生成的Cat模型。

文件上产

使用@ApiBody装饰器和@ApiConsumes()来使能文件上传,这里有一个完整的使用文件上传技术的例子。

@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiBody({
  description: 'List of cats',
  type: FileUploadDto,
})
uploadFile(@UploadedFile() file) {}

FileUploadDto像这样定义:

class FileUploadDto {
  @ApiProperty({ type: 'string', format: 'binary' })
  file: any;
}

要处理多个文件上传,如下定义FilesUploadDto

class FilesUploadDto {
  @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } })
  files: any[];
}

扩展

要为请求增加一个扩展使用@ApiExtension()装饰器. 该扩展名称必须以 x-前缀。

@ApiExtension('x-foo', { hello: 'world' })

高级主题:通用ApiResponse

基于原始定义,的能力,我们可以为Swagger定义通用原型:

export class PaginatedDto<TData> {
  @ApiProperty()
  total: number;

  @ApiProperty()
  limit: number;

  @ApiProperty()
  offset: number;

  results: TData[];
}

我们跳过了定义results,因为后面要提供一个原始定义。现在,我们定义另一个DTO,例如CatDto如下:

export class CatDto {
  @ApiProperty()
  name: string;

  @ApiProperty()
  age: number;

  @ApiProperty()
  breed: string;
}

我们可以定义一个PaginatedDto<CatDto>响应如下:

@ApiOkResponse({
  schema: {
    allOf: [
      { $ref: getSchemaPath(PaginatedDto) },
      {
        properties: {
          results: {
            type: 'array',
            items: { $ref: getSchemaPath(CatDto) },
          },
        },
      },
    ],
  },
})
async findAll(): Promise<PaginatedDto<CatDto>> {}

在这个例子中,我们指定响应拥有所有的PaginatedDto并且results属性类型为CatDto数组。

  • getSchemaPath() 函数从一个给定模型的OpenAPI指定文件返回OpenAPI原型路径
  • allOf是一个OAS3的概念,包括各种各样相关用例的继承。

最后,因为PaginatedDto没有被任何控制器直接引用,SwaggerModule还不能生成一个相应的模型定义。我们需要一个额外的模型,可以在控制器水平使用@ApiExtraModels()装饰器。

@Controller('cats')
@ApiExtraModels(PaginatedDto)
export class CatsController {}

如果你现在运行Swagger,为任何终端生成的swagger.json文件看上去应该像定义的这样:

"responses": {
  "200": {
    "description": "",
    "content": {
      "application/json": {
        "schema": {
          "allOf": [
            {
              "$ref": "#/components/schemas/PaginatedDto"
            },
            {
              "properties": {
                "results": {
                  "$ref": "#/components/schemas/CatDto"
                }
              }
            }
          ]
        }
      }
    }
  }
}

为了让其可重用,我们为PaginatedDto像这样创建一个装饰器:

export const ApiPaginatedResponse = <TModel extends Type<any>>(
  model: TModel,
) => {
  return applyDecorators(
    ApiOkResponse({
      schema: {
        allOf: [
          { $ref: getSchemaPath(PaginatedDto) },
          {
            properties: {
              results: {
                type: 'array',
                items: { $ref: getSchemaPath(model) },
              },
            },
          },
        ],
      },
    }),
  );
};

?> Type<any>接口和applyDecorators函数从@nestjs/common引入.

我们现在可以为终端使用自定义的@ApiPaginatedResponse()装饰器 :

@ApiPaginatedResponse(CatDto)
async findAll(): Promise<PaginatedDto<CatDto>> {}

作为客户端生成工具,这一方法为客户端提供了一个含糊的PaginatedResponse<TModel>。下面示例展示了生成的客户端访问GET /终端的结果。

// Angular
findAll(): Observable<{ total: number, limit: number, offset: number, results: CatDto[] }>

可以看出,这里的返回类型是含糊不清的。要处理这个问题,可以为ApiPaginatedResponse原型添加title属性。

export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => {
  return applyDecorators(
    ApiOkResponse({
      schema: {
        title: `PaginatedResponseOf${model.name}`
        allOf: [
          // ...
        ],
      },
    }),
  );
};

现在结果变成了。

// Angular
findAll(): Observable<PaginatedResponseOfCatDto>

安全

要确定某个特定操作使用哪个安全机制,使用@ApiSecurity()装饰器。

@ApiSecurity('basic')
@Controller('cats')
export class CatsController {}

在运行程序前,使用DocumentBuilder在基础文档里添加安全定义。

const options = new DocumentBuilder().addSecurity('basic', {
  type: 'http',
  scheme: 'basic',
});

一些最常用的认证机制是内置的(例如basicbearer),因此不需要像上面那样手动定义。

Basic认证

使用@ApiBasicAuth()配置basic认证。

@ApiBasicAuth()
@Controller('cats')
export class CatsController {}

在运行程序前,使用DocumentBuilder在基础文档里添加安全定义。

const options = new DocumentBuilder().addBasicAuth();

Bearer认证

使用@ApiBearerAuth()启用bearer认证。

@ApiBearerAuth()
@Controller('cats')
export class CatsController {}

在运行程序前,使用DocumentBuilder在基础文档里添加安全定义。

const opt
ions = new DocumentBuilder().addBearerAuth();

OAuth2认证

使用@ApiOAuth2()启用OAuth2认证。

@ApiOAuth2(['pets:write'])
@Controller('cats')
export class CatsController {}

在运行程序前,使用DocumentBuilder在基础文档里添加安全定义。

const options = new DocumentBuilder().addOAuth2();

Cookie认证

使用@ApiCookieAuth()启用cookie认证。

@ApiCookieAuth()
@Controller('cats')
export class CatsController {}

在运行程序前,使用DocumentBuilder在基础文档里添加安全定义。

const options = new DocumentBuilder().addCookieAuth('optional-session-id');

映射的类型

像构建CRUD特性一样,通常需要基于实体类型创建变体。Nest提供了一些应用函数来进行类型变换,以让这类变换工作更简单。

Partial(部分声明)

在创建数据转换对象(也称为DTO),将创建更新创建为同一类型通常很有用。例如,创建变体可能需要所有字段,但更新变体可能将所有字段都配置为可选的。

Nest提供了PartialType()应用函数让这一任务更简单地最小化构造。

PartialType()函数返回一个类型(类)将输入的所有属性配置为可选的。例如,你可以这样创建一个类型。

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty()
  name: string;

  @ApiProperty()
  age: number;

  @ApiProperty()
  breed: string;
}

默认所有的字段都是必须的。要创建一个所有字段与之相同但都是可选的字段,使用PartialType()并将CreateCatDto作为参数。

export class UpdateCatDto extends PartialType(CreateCatDto) {}

?> PartialType()函数从@nestjs/swagger引入.

Pick(拾取)

PickType()函数从输入类型中拾取一部分属性并生成一个新类型(类) 。假设我们起始类如下:

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty()
  name: string;

  @ApiProperty()
  age: number;

  @ApiProperty()
  breed: string;
}

我们使用PickType()从中拾取一部分属性:

export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {}

?> PickType()函数 从@nestjs/swagger引入.

Omit(省略)

OmitType()函数拾取所有输入属性,移除指定部分属性。例如,我们起始类型如下:

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty()
  name: string;

  @ApiProperty()
  age: number;

  @ApiProperty()
  breed: string;
}

我们可以以此创建一个除name之外的包含其他所有属性的类。OmitType函数的第二个参数是包含要移除属性名称的数组。

export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {}

?> OmitType()函数从@nestjs/swagger引入.

Intersection(交叉)

IntersectionType()函数将两个类型组合为一个类型(类),例如,我们起始的两个类型如下:

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty()
  name: string;

  @ApiProperty()
  breed: string;
}

export class AdditionalCatInfo {
  @ApiProperty()
  color: string;
}

我们可以生成一个由两个类中所有属性组成的新类型。

export class UpdateCatDto extends IntersectionType(
  CreateCatDto,
  AdditionalCatInfo,
) {}

?>IntersectionType()函数从@nestjs/swagger引入.

Composition(组合)

映射类型的使用时可以组合的,例如,以下代码创建一个类型(类),它包含了CreateCatDto除了name之外的所有属性,并将所有属性设置为可选的。

export class UpdateCatDto extends PartialType(
  OmitType(CreateCatDto, ['name'] as const),
) {}

装饰器

所有可用的OpenAPI装饰器都有Api前缀用以和核心装饰器区分。下面是完整的装饰器名称列表以及其可能能应用的范围。

名称 类型
@ApiOperation() Method
@ApiResponse() Method / Controller
@ApiProduces() Method / Controller
@ApiConsumes() Method / Controller
@ApiBearerAuth() Method / Controller
@ApiOAuth2() Method / Controller
@ApiBasicAuth() Method / Controller
@ApiSecurity() Method / Controller
@ApiExtraModels() Method / Controller
@ApiBody() Method
@ApiParam() Method
@ApiQuery() Method
@ApiHeader() Method / Controller
@ApiExcludeEndpoint() Method
@ApiTags() Method / Controller
@ApiProperty() Model
@ApiPropertyOptional() Model
@ApiHideProperty() Model
@ApiExtension() Method

CLI插件

TypeScript的元数据反射系统有一些限制,一些功能因此不可能实现,例如确定一个类由哪些属性组成,或者一个属性是可选的还是必须的。然而,一些限制可以在编译时强调。Nest提供了一个增强TypeScript编译过程的插件来减少需要的原型代码量。

?> 这个插件是一个opt-in,你也可以选择手动声明所有的装饰器,或者仅仅声明你需要的。

概述

Swagger插件可以自动:

  • 使用@ApiProperty注释所有除了用@ApiHideProperty装饰的DTO属性。
  • 根据问号符号确定required属性(例如 name?: string 将设置required: false)
  • 根据类型配置typeenum(也支持数组)
  • 基于给定的默认值配置默认参数
  • 基于class-validator装饰器配置一些验证策略(如果classValidatorShim配置为true)
  • 为每个终端添加一个响应装饰器,包括合适的状态和类型(响应模式)
  • 根据注释生成属性和终端的描述(如果introspectComments配置为true)
  • 基于注释生成属性的示例数据(如果introspectComments配置为true)

注意,你的文件名必须有如下后缀: ['.dto.ts', '.entity.ts'] (例如create-user.dto.ts) 才能被插件分析。

如果使用其他后缀,你可以调整插件属性来指定dtoFileNameSuffix选项(见下文)。

之前,如果你想要通过Swagger提供一个交互体验,你必须复制大量代码让包知道你的模型/组件在该声明中。例如,你可以定义一个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;
}

插件可以通过抽象语法树添加合适的装饰器,你不在需要在代码中到处写ApiProperty装饰器。

?> 插件可以自动生成所有缺失的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[] = [];

使用CLI插件

要使能CLI插件,打开nest-cli.json (如果你在用Nest CLI)并添加以下插件配置:

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

你可以使用其他options属性来自定义插件特性。

"plugins": [
  {
    "name": "@nestjs/swagger",
    "options": {
      "classValidatorShim": false,
      "introspectComments": true
    }
  }
]

options属性实现以下接口:

export interface PluginOptions {
  dtoFileNameSuffix?: string[];
  controllerFileNameSuffix?: string[];
  classValidatorShim?: boolean;
  introspectComments?: boolean;
}
选项 默认 说明
dtoFileNameSuffix ['.dto.ts', '.entity.ts'] DTO (数据传输对象)文件后缀
controllerFileNameSuffix .controller.ts 控制文件后缀
classValidatorShim true 如果配置为true,模块将重用class-validator验证装饰器 (例如@Max(10) 将在schema定义中增加max: 10)
introspectComments false 如果配置为true,插件将根据描述注释生成说明和示例

如果不使用CLI,但是使用一个用户定义的Webpack配置,可以和ts-loader配合使用该插件:

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

ts-jest(e2e)

要运行e2e测试,ts-jest在内存汇总编译源码,这意味着不使用Nest Cli编译,不应用任何插件或AST转换,要使用插件,在e2e测试目录下创建以下文件:

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

module.exports.name = 'nestjs-swagger-transformer';
// you should change the version number anytime you change the configuration below - otherwise, jest will not detect changes
module.exports.version = 1;

module.exports.factory = (cs) => {
  return transformer.before(
    {
      // @nestjs/swagger/plugin options (can be empty)
    },
    cs.tsCompiler.program,
  );
};

在jest配置文件中引入AST变换。默认在(启动应用中),e2e测试配置文件在测试目录下,名为jest-e2e.json

{
  ... // other configuration
  "globals": {
    "ts-jest": {
      "astTransformers": {
        "before": ["<path to the file created above>"],
      }
    }
  }
}

其他特性

全局前缀

要忽略一个通过setGlobalPrefix()配置的全局前缀, 使用ignoreGlobalPrefix:

const document = SwaggerModule.createDocument(app, options, {
  ignoreGlobalPrefix: true,
});

多重声明

Swagger模块提供了一个支持多重声明的方法,也就是说可以在多个终端提供多个界面和多个文档。

要支持多重声明,首先在模块中要进行声明,在createDocument()方法中传递第3个参数,extraOptions,这是个包含一个叫做include名称的属性,该属性提供了一个由模块组成的数组。

可以如下配置多重声明:

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  /**
   * createDocument(application, configurationOptions, extraOptions);
   *
   * createDocument method takes an optional 3rd argument "extraOptions"
   * which is an object with "include" property where you can pass an Array
   * of Modules that you want to include in that Swagger Specification
   * E.g: CatsModule and DogsModule will have two separate Swagger Specifications which
   * will be exposed on two different SwaggerUI with two different endpoints.
   */

  const options = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .build();

  const catDocument = SwaggerModule.createDocument(app, options, {
    include: [CatsModule],
  });
  SwaggerModule.setup('api/cats', app, catDocument);

  const secondOptions = new DocumentBuilder()
    .setTitle('Dogs example')
    .setDescription('The dogs API description')
    .setVersion('1.0')
    .addTag('dogs')
    .build();

  const dogDocument = SwaggerModule.createDocument(app, secondOptions, {
    include: [DogsModule],
  });
  SwaggerModule.setup('api/dogs', app, dogDocument);

  await app.listen(3000);
}
bootstrap();

现在可以使用以下命令启动服务器:

$ npm run start

访问http://localhost:3000/api/cats可以看到catsSwagger界面, 访问http://localhost:3000/api/dogs可以看到dogsSwagger界面。

迁移指南

如果你在使用@nestjs/swagger@3.*, 注意在4.0版本中有以下破坏性变化或者更改。

破坏性变化

以下装饰器被改变/重命名

  • @ApiModelProperty现在是@ApiProperty
  • @ApiModelPropertyOptional现在是@ApiPropertyOptional
  • @ApiResponseModelProperty现在是@ApiResponseProperty
  • @ApiImplicitQuery现在是@ApiQuery
  • @ApiImplicitParam现在是@ApiParam
  • @ApiImplicitBody现在是@ApiBody
  • @ApiImplicitHeader现在是@ApiHeader
  • @ApiOperation({ title: 'test' })现在是@ApiOperation({ summary: 'test' })
  • @ApiUseTags现在是@ApiTags

DocumentBuilder的破坏性更新(升级了方法签名):

  • addTag
  • addBearerAuth
  • addOAuth2
  • setContactEmail现在是setContact
  • setHost has been removed
  • setSchemes has been removed (使用`addServer instead, e.g., addServer('http://'))

新方法

添加了以下新方法:

  • addServer
  • addApiKey
  • addBasicAuth
  • addSecurity
  • addSecurityRequirements

译者署名

用户名 头像 职能 签名
@weizy0219 翻译 专注于TypeScript全栈、物联网和Python数据科学,@weizhiyong