Operations

In OpenAPI terms, paths are endpoints (resources), such as /users or /reports/summary, that your API exposes, and operations are the HTTP methods used to manipulate these paths, such as GET, POST or DELETE.

Tags

To attach a controller to a specific tag, use the @ApiTags(...tags) decorator.

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

Headers

To define custom headers that are expected as part of the request, use @ApiHeader().

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

Responses

To define a custom HTTP response, use the @ApiResponse() decorator.

  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 provides a set of short-hand API response decorators that inherit from the @ApiResponse decorator:

  • @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. }

To specify a return model for a request, we must create a class and annotate all properties with the @ApiProperty() decorator.

  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. }

Then the Cat model can be used in combination with the type property of the response decorator.

  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. }

Let’s open the browser and verify the generated Cat model:

Operations - 图1

File upload

You can enable file upload for a specific method with the @ApiBody decorator together with @ApiConsumes(). Here’s a full example using the File Upload technique:

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

Where FileUploadDto is defined as follows:

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

To handle multiple files uploading, you can define FilesUploadDto as follows:

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

Extensions

To add an Extension to a request use the @ApiExtension() decorator. The extension name must be prefixed with x-.

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

Advanced: Generic ApiResponse

With the ability to provide Raw Definitions, we can define Generic schema for Swagger UI. Assume we have the following DTO:

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

We skip decorating results as we will be providing a raw definition for it later. Now, let’s define another DTO and name it, for example, CatDto, as follows:

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

With this in place, we can define a PaginatedDto<CatDto> response, as follows:

  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>> {}

In this example, we specify that the response will have allOf PaginatedDto and the results property will be of type Array<CatDto>.

  • getSchemaPath() function that returns the OpenAPI Schema path from within the OpenAPI Spec File for a given model.
  • allOf is a concept that OAS 3 provides to cover various Inheritance related use-cases.

Lastly, since PaginatedDto is not directly referenced by any controller, the SwaggerModule will not be able to generate a corresponding model definition just yet. In this case, we must add it as an Extra Model. For example, we can use the @ApiExtraModels() decorator on the controller level, as follows:

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

If you run Swagger now, the generated swagger.json for this specific endpoint should have the following response defined:

  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. }

To make it reusable, we can create a custom decorator for PaginatedDto, as follows:

  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. };

info Hint Type<any> interface and applyDecorators function are imported from the @nestjs/common package.

With this in place, we can use the custom @ApiPaginatedResponse() decorator on our endpoint:

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

For client generation tools, this approach poses an ambiguity in how the PaginatedResponse<TModel> is being generated for the client. The following snippet is an example of a client generator result for the above GET / endpoint.

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

As you can see, the Return Type here is ambiguous. To workaround this issue, you can add a title property to the schema for ApiPaginatedResponse:

  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. };

Now the result of the client generator tool will become:

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