OPENAPI

介绍

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

安装

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

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

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

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

引导

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

  1. import { NestFactory } from '@nestjs/core';
  2. import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
  3. import { AppModule } from './app.module';
  4. async function bootstrap() {
  5. const app = await NestFactory.create(AppModule);
  6. const config = new DocumentBuilder()
  7. .setTitle('Cats example')
  8. .setDescription('The cats API description')
  9. .setVersion('1.0')
  10. .addTag('cats')
  11. .build();
  12. const document = SwaggerModule.createDocument(app, config);
  13. SwaggerModule.setup('api', app, document);
  14. await app.listen(3000);
  15. }
  16. bootstrap();

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

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

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

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

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

  1. $ 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。

  1. app.register(helmet, {
  2. contentSecurityPolicy: {
  3. directives: {
  4. defaultSrc: [`'self'`],
  5. styleSrc: [`'self'`, `'unsafe-inline'`],
  6. imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
  7. scriptSrc: [`'self'`, `https: 'unsafe-inline'`],
  8. },
  9. },
  10. });

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

文档选项

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

  1. export interface SwaggerDocumentOptions {
  2. /**
  3. * List of modules to include in the specification
  4. */
  5. include?: Function[];
  6. /**
  7. * Additional, extra models that should be inspected and included in the specification
  8. */
  9. extraModels?: Function[];
  10. /**
  11. * If `true`, swagger will ignore the global prefix set through `setGlobalPrefix()` method
  12. */
  13. ignoreGlobalPrefix?: boolean;
  14. /**
  15. * If `true`, swagger will also load routes from the modules imported by `include` modules
  16. */
  17. deepScanRoutes?: boolean;
  18. /**
  19. * Custom operationIdFactory that will be used to generate the `operationId`
  20. * based on the `controllerKey` and `methodKey`
  21. * @default () => controllerKey_methodKey
  22. */
  23. operationIdFactory?: (controllerKey: string, methodKey: string) => string;
  24. }

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

  1. const options: SwaggerDocumentOptions = {
  2. operationIdFactory: (
  3. controllerKey: string,
  4. methodKey: string
  5. ) => methodKey
  6. });
  7. const document = SwaggerModule.createDocument(app, config, options);

示例

一个例子见这里

类型和参数

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

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

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

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

OPENAPI - 图2

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

  1. import { ApiProperty } from '@nestjs/swagger';
  2. export class CreateCatDto {
  3. @ApiProperty()
  4. name: string;
  5. @ApiProperty()
  6. age: number;
  7. @ApiProperty()
  8. breed: string;
  9. }

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

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

OPENAPI - 图3

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

  1. @ApiProperty({
  2. description: 'The age of a cat',
  3. minimum: 1,
  4. default: 1,
  5. })
  6. age: number;

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

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

  1. @ApiProperty({
  2. type: Number,
  3. })
  4. age: number;

数组

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

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

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

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

循环依赖

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

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

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

泛型和接口

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

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

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

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

枚举

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

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

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

  1. export enum UserRole {
  2. Admin = 'Admin',
  3. Moderator = 'Moderator',
  4. User = 'User',
  5. }

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

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

OPENAPI - 图4

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

OPENAPI - 图5

枚举原型

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

  1. - breed:
  2. type: 'string'
  3. enum:
  4. - Persian
  5. - Tabby
  6. - Siamese

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

  1. // generated client-side code
  2. export class CatDetail {
  3. breed: CatDetailEnum;
  4. }
  5. export class CatInformation {
  6. breed: CatInformationEnum;
  7. }
  8. export enum CatDetailEnum {
  9. Persian = 'Persian',
  10. Tabby = 'Tabby',
  11. Siamese = 'Siamese',
  12. }
  13. export enum CatInformationEnum {
  14. Persian = 'Persian',
  15. Tabby = 'Tabby',
  16. Siamese = 'Siamese',
  17. }

上述代码使用NSwag工具生成

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

  1. export class CatDetail {
  2. @ApiProperty({ enum: CatBreed, enumName: 'CatBreed' })
  3. breed: CatBreed;
  4. }

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

  1. CatDetail:
  2. type: 'object'
  3. properties:
  4. ...
  5. - breed:
  6. schema:
  7. $ref: '#/components/schemas/CatBreed'
  8. CatBreed:
  9. type: string
  10. enum:
  11. - Persian
  12. - Tabby
  13. - Siamese

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

原始定义

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

  1. @ApiProperty({
  2. type: 'array',
  3. items: {
  4. type: 'array',
  5. items: {
  6. type: 'number',
  7. },
  8. },
  9. })
  10. coords: number[][];

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

  1. @ApiBody({
  2. schema: {
  3. type: 'array',
  4. items: {
  5. type: 'array',
  6. items: {
  7. type: 'number',
  8. },
  9. },
  10. },
  11. })
  12. async create(@Body() coords: number[][]) {}

额外模型

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

  1. @ApiExtraModels(ExtraModel)
  2. export class CreateCatDto {}

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

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

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

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

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

oneOf, anyOf, allOf

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

  1. @ApiProperty({
  2. oneOf: [
  3. { $ref: getSchemaPath(Cat) },
  4. { $ref: getSchemaPath(Dog) },
  5. ],
  6. })
  7. pet: Cat | Dog;

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

  1. type Pet = Cat | Dog;
  2. @ApiProperty({
  3. type: 'array',
  4. items: {
  5. oneOf: [
  6. { $ref: getSchemaPath(Cat) },
  7. { $ref: getSchemaPath(Dog) },
  8. ],
  9. },
  10. })
  11. pets: Pet[];

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

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

操作

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

标签

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

  1. @ApiTags('cats')
  2. @Controller('cats')
  3. export class CatsController {}

报头

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

  1. @ApiHeader({
  2. name: 'X-MyHeader',
  3. description: 'Custom header',
  4. })
  5. @Controller('cats')
  6. export class CatsController {}

响应

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

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

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()
  1. @Post()
  2. @ApiCreatedResponse({ description: 'The record has been successfully created.'})
  3. @ApiForbiddenResponse({ description: 'Forbidden.'})
  4. async create(@Body() createCatDto: CreateCatDto) {
  5. this.catsService.create(createCatDto);
  6. }

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

  1. export class Cat {
  2. @ApiProperty()
  3. id: number;
  4. @ApiProperty()
  5. name: string;
  6. @ApiProperty()
  7. age: number;
  8. @ApiProperty()
  9. breed: string;
  10. }

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

  1. @ApiTags('cats')
  2. @Controller('cats')
  3. export class CatsController {
  4. @Post()
  5. @ApiCreatedResponse({
  6. description: 'The record has been successfully created.',
  7. type: Cat,
  8. })
  9. async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
  10. return this.catsService.create(createCatDto);
  11. }
  12. }

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

OPENAPI - 图6

文件上传

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

  1. @UseInterceptors(FileInterceptor('file'))
  2. @ApiConsumes('multipart/form-data')
  3. @ApiBody({
  4. description: 'List of cats',
  5. type: FileUploadDto,
  6. })
  7. uploadFile(@UploadedFile() file) {}

FileUploadDto像这样定义:

  1. class FileUploadDto {
  2. @ApiProperty({ type: 'string', format: 'binary' })
  3. file: any;
  4. }

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

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

扩展

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

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

高级主题:通用ApiResponse

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

  1. export class PaginatedDto<TData> {
  2. @ApiProperty()
  3. total: number;
  4. @ApiProperty()
  5. limit: number;
  6. @ApiProperty()
  7. offset: number;
  8. results: TData[];
  9. }

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

  1. export class CatDto {
  2. @ApiProperty()
  3. name: string;
  4. @ApiProperty()
  5. age: number;
  6. @ApiProperty()
  7. breed: string;
  8. }

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

  1. @ApiOkResponse({
  2. schema: {
  3. allOf: [
  4. { $ref: getSchemaPath(PaginatedDto) },
  5. {
  6. properties: {
  7. results: {
  8. type: 'array',
  9. items: { $ref: getSchemaPath(CatDto) },
  10. },
  11. },
  12. },
  13. ],
  14. },
  15. })
  16. async findAll(): Promise<PaginatedDto<CatDto>> {}

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

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

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

  1. @Controller('cats')
  2. @ApiExtraModels(PaginatedDto)
  3. export class CatsController {}

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

  1. "responses": {
  2. "200": {
  3. "description": "",
  4. "content": {
  5. "application/json": {
  6. "schema": {
  7. "allOf": [
  8. {
  9. "$ref": "#/components/schemas/PaginatedDto"
  10. },
  11. {
  12. "properties": {
  13. "results": {
  14. "$ref": "#/components/schemas/CatDto"
  15. }
  16. }
  17. }
  18. ]
  19. }
  20. }
  21. }
  22. }
  23. }

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

  1. export const ApiPaginatedResponse = <TModel extends Type<any>>(
  2. model: TModel,
  3. ) => {
  4. return applyDecorators(
  5. ApiOkResponse({
  6. schema: {
  7. allOf: [
  8. { $ref: getSchemaPath(PaginatedDto) },
  9. {
  10. properties: {
  11. results: {
  12. type: 'array',
  13. items: { $ref: getSchemaPath(model) },
  14. },
  15. },
  16. },
  17. ],
  18. },
  19. }),
  20. );
  21. };

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

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

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

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

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

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

  1. export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => {
  2. return applyDecorators(
  3. ApiOkResponse({
  4. schema: {
  5. title: `PaginatedResponseOf${model.name}`
  6. allOf: [
  7. // ...
  8. ],
  9. },
  10. }),
  11. );
  12. };

现在结果变成了。

  1. // Angular
  2. findAll(): Observable<PaginatedResponseOfCatDto>

安全

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

  1. @ApiSecurity('basic')
  2. @Controller('cats')
  3. export class CatsController {}

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

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

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

Basic认证

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

  1. @ApiBasicAuth()
  2. @Controller('cats')
  3. export class CatsController {}

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

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

Bearer认证

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

  1. @ApiBearerAuth()
  2. @Controller('cats')
  3. export class CatsController {}

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

  1. const options = new DocumentBuilder().addBearerAuth();

OAuth2认证

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

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

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

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

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

  1. @ApiCookieAuth()
  2. @Controller('cats')
  3. export class CatsController {}

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

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

映射的类型

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

Partial(部分声明)

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

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

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

  1. import { ApiProperty } from '@nestjs/swagger';
  2. export class CreateCatDto {
  3. @ApiProperty()
  4. name: string;
  5. @ApiProperty()
  6. age: number;
  7. @ApiProperty()
  8. breed: string;
  9. }

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

  1. export class UpdateCatDto extends PartialType(CreateCatDto) {}

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

Pick(拾取)

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

  1. import { ApiProperty } from '@nestjs/swagger';
  2. export class CreateCatDto {
  3. @ApiProperty()
  4. name: string;
  5. @ApiProperty()
  6. age: number;
  7. @ApiProperty()
  8. breed: string;
  9. }

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

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

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

Omit(省略)

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

  1. import { ApiProperty } from '@nestjs/swagger';
  2. export class CreateCatDto {
  3. @ApiProperty()
  4. name: string;
  5. @ApiProperty()
  6. age: number;
  7. @ApiProperty()
  8. breed: string;
  9. }

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

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

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

Intersection(交叉)

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

  1. import { ApiProperty } from '@nestjs/swagger';
  2. export class CreateCatDto {
  3. @ApiProperty()
  4. name: string;
  5. @ApiProperty()
  6. breed: string;
  7. }
  8. export class AdditionalCatInfo {
  9. @ApiProperty()
  10. color: string;
  11. }

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

  1. export class UpdateCatDto extends IntersectionType(
  2. CreateCatDto,
  3. AdditionalCatInfo,
  4. ) {}

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

Composition(组合)

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

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

装饰器

所有可用的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类:

  1. export class CreateUserDto {
  2. @ApiProperty()
  3. email: string;
  4. @ApiProperty()
  5. password: string;
  6. @ApiProperty({ enum: RoleEnum, default: [], isArray: true })
  7. roles: RoleEnum[] = [];
  8. @ApiProperty({ required: false, default: true })
  9. isEnabled?: boolean = true;
  10. }

在中等项目中这还不是问题,但是一旦有大量类的话这就变得冗余而难以维护。

要应用Swagger插件,可以简单声明上述类定义:

  1. export class CreateUserDto {
  2. email: string;
  3. password: string;
  4. roles: RoleEnum[] = [];
  5. isEnabled?: boolean = true;
  6. }

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

插件可以自动生成所有缺失的swagger属性,但是如果你要覆盖他们,只需要通过@ApiProperty()显式声明即可。

注释自省

注释自省特性使能后,CLI插件可以基于注释生成描述和示例值。

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

  1. /**
  2. * A list of user's roles
  3. * @example ['admin']
  4. */
  5. @ApiProperty({
  6. description: `A list of user's roles`,
  7. example: ['admin'],
  8. })
  9. roles: RoleEnum[] = [];

你必须复制描述和示例值。当introspectComments使能后,CLI插件可以自动解压这些注释并提供描述(以及示例,如果定义了的话)。现在,上述属性可以简化为:

  1. /**
  2. * A list of user's roles
  3. * @example ['admin']
  4. */
  5. roles: RoleEnum[] = [];

使用CLI插件

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

  1. {
  2. "collection": "@nestjs/schematics",
  3. "sourceRoot": "src",
  4. "compilerOptions": {
  5. "plugins": ["@nestjs/swagger"]
  6. }
  7. }

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

  1. "plugins": [
  2. {
  3. "name": "@nestjs/swagger",
  4. "options": {
  5. "classValidatorShim": false,
  6. "introspectComments": true
  7. }
  8. }
  9. ]

options属性实现以下接口:

  1. export interface PluginOptions {
  2. dtoFileNameSuffix?: string[];
  3. controllerFileNameSuffix?: string[];
  4. classValidatorShim?: boolean;
  5. introspectComments?: boolean;
  6. }
选项 默认 说明
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配合使用该插件:

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

ts-jest(e2e)

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

  1. const transformer = require('@nestjs/swagger/plugin');
  2. module.exports.name = 'nestjs-swagger-transformer';
  3. // you should change the version number anytime you change the configuration below - otherwise, jest will not detect changes
  4. module.exports.version = 1;
  5. module.exports.factory = (cs) => {
  6. return transformer.before(
  7. {
  8. // @nestjs/swagger/plugin options (can be empty)
  9. },
  10. cs.tsCompiler.program,
  11. );
  12. };

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

  1. {
  2. ... // other configuration
  3. "globals": {
  4. "ts-jest": {
  5. "astTransformers": {
  6. "before": ["<path to the file created above>"],
  7. }
  8. }
  9. }
  10. }

其他特性

全局前缀

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

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

多重声明

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

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

可以如下配置多重声明:

  1. import { NestFactory } from '@nestjs/core';
  2. import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
  3. import { AppModule } from './app.module';
  4. async function bootstrap() {
  5. const app = await NestFactory.create(AppModule);
  6. /**
  7. * createDocument(application, configurationOptions, extraOptions);
  8. *
  9. * createDocument method takes an optional 3rd argument "extraOptions"
  10. * which is an object with "include" property where you can pass an Array
  11. * of Modules that you want to include in that Swagger Specification
  12. * E.g: CatsModule and DogsModule will have two separate Swagger Specifications which
  13. * will be exposed on two different SwaggerUI with two different endpoints.
  14. */
  15. const options = new DocumentBuilder()
  16. .setTitle('Cats example')
  17. .setDescription('The cats API description')
  18. .setVersion('1.0')
  19. .addTag('cats')
  20. .build();
  21. const catDocument = SwaggerModule.createDocument(app, options, {
  22. include: [CatsModule],
  23. });
  24. SwaggerModule.setup('api/cats', app, catDocument);
  25. const secondOptions = new DocumentBuilder()
  26. .setTitle('Dogs example')
  27. .setDescription('The dogs API description')
  28. .setVersion('1.0')
  29. .addTag('dogs')
  30. .build();
  31. const dogDocument = SwaggerModule.createDocument(app, secondOptions, {
  32. include: [DogsModule],
  33. });
  34. SwaggerModule.setup('api/dogs', app, dogDocument);
  35. await app.listen(3000);
  36. }
  37. bootstrap();

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

  1. $ 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

译者署名

| 用户名 | 头像 | 职能 | 签名 | |—-|—-|—-|—-|