NestJS Logo
NestJS 中文文档
v10.0.0
  • 介绍
  • 快速上手
  • 控制器
  • 提供者
  • 模块
  • 中间件
  • 异常过滤器
  • 管道
  • 守卫
  • 拦截器
  • 自定义装饰器
  • 自定义提供者
  • 异步提供者
  • 动态模块
  • 依赖注入作用域
  • 循环依赖
  • 模块引用
  • 懒加载模块
  • 执行上下文
  • 生命周期事件
  • 发现服务
  • 跨平台无关性
  • 测试
迁移指南
API 参考
官方课程
  1. 文档
  2. OpenAPI
  3. 操作

类型和参数
安全性

操作

在 OpenAPI 中,paths 是指 API 暴露的端点(例如 /users 或 /reports/summary),而 operations 则是用于处理这些路径的 HTTP 方法,例如 GET、POST 或 DELETE。

标签

如果要将控制器关联到特定标签,可以使用 @ApiTags(...tags) 装饰器。

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

请求头

如果需要定义请求中期望包含的自定义请求头,可以使用 @ApiHeader() 装饰器。

@ApiHeader({
  name: 'X-MyHeader',
  description: '自定义请求头',
})
@Controller('cats')
export class CatsController {}

响应

你可以使用 @ApiResponse() 装饰器来为端点添加响应的详细描述。

@Post()
@ApiResponse({ status: 201, description: '资源已成功创建。' })
@ApiResponse({ status: 403, description: '无权访问此资源。' })
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto)
}

为了简化常见 HTTP 响应的声明,Nest 提供了一系列派生自 @ApiResponse() 的简写装饰器:

  • @ApiOkResponse()
  • @ApiCreatedResponse()
  • @ApiAcceptedResponse()
  • @ApiNoContentResponse()
  • @ApiMovedPermanentlyResponse()
  • @ApiFoundResponse()
  • @ApiBadRequestResponse()
  • @ApiUnauthorizedResponse()
  • @ApiNotFoundResponse()
  • @ApiForbiddenResponse()
  • @ApiMethodNotAllowedResponse()
  • @ApiNotAcceptableResponse()
  • @ApiRequestTimeoutResponse()
  • @ApiConflictResponse()
  • @ApiPreconditionFailedResponse()
  • @ApiTooManyRequestsResponse()
  • @ApiGoneResponse()
  • @ApiPayloadTooLargeResponse()
  • @ApiUnsupportedMediaTypeResponse()
  • @ApiUnprocessableEntityResponse()
  • @ApiInternalServerErrorResponse()
  • @ApiNotImplementedResponse()
  • @ApiBadGatewayResponse()
  • @ApiServiceUnavailableResponse()
  • @ApiGatewayTimeoutResponse()
  • @ApiDefaultResponse()

使用简写装饰器,上面的示例可以改写为:

@Post()
@ApiCreatedResponse({ description: '资源已成功创建。' })
@ApiForbiddenResponse({ description: '无权访问此资源。' })
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto)
}

响应模型

要让 Swagger UI 展示 API 响应的数据结构,你需要先定义一个模型或 DTO 类,并使用 @ApiProperty() 装饰器注解其所有属性。

export class Cat {
  @ApiProperty({ description: '猫的 ID', example: 1 })
  id: number

  @ApiProperty({ description: '猫的名字', example: 'Mimi' })
  name: string

  @ApiProperty({ description: '猫的年龄', example: 2 })
  age: number

  @ApiProperty({ description: '猫的品种', example: '布偶' })
  breed: string
}

然后,在响应装饰器(如 @ApiCreatedResponse)中,通过 type 属性来指定这个模型。

@ApiTags('cats')
@Controller('cats')
export class CatsController {
  @Post()
  @ApiCreatedResponse({
    description: 'Cat 记录已成功创建。',
    type: Cat,
  })
  async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
    return this.catsService.create(createCatDto)
  }
}

配置完成后,访问 Swagger UI,就可以看到 Cat 模型已经被正确地解析和展示为响应体结构:

Swagger Response Type

全局响应

如果你不希望为每个端点或控制器重复定义相同的响应,可以通过 DocumentBuilder 来设置全局响应,这对于统一定义通用响应(例如 401 Unauthorized 或 500 Internal Server Error 等错误)非常有用。

const config = new DocumentBuilder()
  .addGlobalResponse({
    status: 500,
    description: '内部服务器错误',
  })
  // 其他配置
  .build()

文件上传

要启用文件上传功能,你可以组合使用 @ApiBody() 和 @ApiConsumes() 装饰器。下面是一个结合文件上传功能的完整示例:

@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiBody({
  description: '新的猫咪信息',
  type: FileUploadDto,
})
uploadFile(@UploadedFile() file: Express.Multer.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() 装饰器为请求添加「扩展」(Extension)。注意,扩展名必须以 x- 作为前缀。

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

进阶:构建通用的 ApiResponse

我们可以通过提供原始定义的方式,为 Swagger UI 定义通用(或称"泛型")的 Schema。假设有如下一个 DTO:

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

  @ApiProperty()
  limit: number

  @ApiProperty()
  offset: number

  results: TData[]
}

我们没有为 results 属性添加装饰器,因为稍后将通过原始定义(Raw Definition)来指定其类型。接下来,再定义另一个 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 属性的类型为 Array<CatDto>。

  • getSchemaPath() 函数会根据指定的模型类,返回其在 OpenAPI 规范文件中的 Schema 路径。
  • allOf 是 OpenAPI 规范 v3 (OAS 3) 中的一个概念,用于组合和继承 Schema,能够处理多种复杂的场景。

最后,由于没有控制器直接引用 PaginatedDto,SwaggerModule 无法自动为它生成对应的模型定义。因此,我们需要使用 @ApiExtraModels() 装饰器将其显式地声明为额外模型。例如,在控制器层级添加该装饰器:

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

此时,该端点生成的 swagger.json 中的响应定义如下:

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

为了便于复用,我们可以为 PaginatedDto 创建一个自定义装饰器,代码如下:

import { applyDecorators, Type } from '@nestjs/common'
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger'

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

请注意,在上面的自定义装饰器中,我们不仅要注册 PaginatedDto,也要注册传入的 model(例如 CatDto),这样 SwaggerModule 才能为它们都生成对应的模型定义。

这样,我们就可以在端点上使用自定义的 @ApiPaginatedResponse() 装饰器了:

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

然而,对于客户端代码生成工具而言,这种方式生成的 PaginatedResponse<TModel> 类型可能存在歧义。下面是一个基于上述 GET / 端点生成的客户端代码示例:

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

可以看到,这里生成的返回类型是一个匿名对象,缺乏明确的类型名称。为了解决这个问题,我们可以在 ApiPaginatedResponse 的 schema 中添加一个 title 属性:

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

添加 title 后,客户端代码生成工具将生成更清晰的结果:

// Angular
findAll(): Observable<PaginatedResponseOfCatDto>