OpenAPI (Swagger)

This chapter applies only to TypeScript

The OpenAPI (Swagger) specification is a powerful definition format to describe RESTful APIs. Nest provides a dedicated module to work with it.

Installation

Firstly, you have to install the required packages:

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

If you are using fastify, you have to install fastify-swagger instead of swagger-ui-express:

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

Bootstrap

Once the installation process is done, open your bootstrap file (mostly main.ts) and initialize the Swagger using SwaggerModule class:

  1. import { NestFactory } from '@nestjs/core';
  2. import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
  3. import { ApplicationModule } from './app.module';
  4. async function bootstrap() {
  5. const app = await NestFactory.create(ApplicationModule);
  6. const options = 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, options);
  13. SwaggerModule.setup('api', app, document);
  14. await app.listen(3000);
  15. }
  16. bootstrap();

The DocumentBuilder is a helper class that helps to structure a base document for the SwaggerModule. It contains several methods that allow setting such properties like title, description, version, and so on.

In order to create a full document (with defined HTTP routes) we use the createDocument() method of the SwaggerModule class. This method takes two arguments, the application instance and the base Swagger options respectively.

The last step is to call setup(). It accepts sequentially (1) path to mount the Swagger, (2) application instance, and (3) the document that describes the Nest application.

Now you can run the following command to start the HTTP server:

  1. $ npm run start

While the application is running, open your browser and navigate to http://localhost:3000/api. You should see a similar page:

OpenAPI (Swagger.md) - 图1

The SwaggerModule automatically reflects all of your endpoints. In the background, it’s making use of swagger-ui-express and creates a live documentation.

info Hint If you want to download the corresponding Swagger JSON file, you can simply call http://localhost:3000/api-json in your browser (if your Swagger documentation is published under http://localhost:3000/api).

Body, query, path parameters

During the examination of the defined controllers, the SwaggerModule is looking for all used @Body(), @Query(), and @Param() decorators in the route handlers. Hence, the valid document can be created.

Moreover, the module creates the models definitions by taking advantage of the reflection. Take a look at the following code:

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

warning Notice To implicitly set the body definition you can use the @ApiImplicitBody() decorator (@nestjs/swagger package).

Based on the CreateCatDto, the module definition will be created:

OpenAPI (Swagger.md) - 图2

As you can see, the definition is empty although the class has a few declared properties. In order to make the class properties accessible to the SwaggerModule, we have to mark all of them with @ApiModelProperty() decorator:

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

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

OpenAPI (Swagger.md) - 图3

The @ApiModelProperty() decorator accepts the following options object:

  1. export const ApiModelProperty: (metadata?: {
  2. description?: string;
  3. required?: boolean;
  4. type?: any;
  5. isArray?: boolean;
  6. collectionFormat?: string;
  7. default?: any;
  8. enum?: SwaggerEnumType;
  9. format?: string;
  10. multipleOf?: number;
  11. maximum?: number;
  12. exclusiveMaximum?: number;
  13. minimum?: number;
  14. exclusiveMinimum?: number;
  15. maxLength?: number;
  16. minLength?: number;
  17. pattern?: string;
  18. maxItems?: number;
  19. minItems?: number;
  20. uniqueItems?: boolean;
  21. maxProperties?: number;
  22. minProperties?: number;
  23. readOnly?: boolean;
  24. xml?: any;
  25. example?: any;
  26. }) => PropertyDecorator;

warning Hint There’s an @ApiModelPropertyOptional() shortcut decorator which helps to avoid continuous typing {{"@ApiModelProperty({ required: false })"}}.

Thanks to that we can simply set the default value, determine whether the property is required or explicitly set the type.

Multiple specifications

Swagger module also provides a way to support multiple specifications. In other words, you can serve different documentations with different SwaggerUI on different endpoints.

In order to allow SwaggerModule to support multi-spec, your application must be written with modular approach. The createDocument() method takes in a 3rd argument: extraOptions which is an object where a property include expects an array of modules.

You can setup Multiple Specifications support as shown below:

  1. import { NestFactory } from '@nestjs/core';
  2. import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
  3. import { ApplicationModule } from './app.module';
  4. async function bootstrap() {
  5. const app = await NestFactory.create(ApplicationModule);
  6. /**
  7. * createDocument(application, configurationOptions, extraOptions);
  8. *
  9. * createDocument method takes in 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();

Now you can start your server with the following command:

  1. $ npm run start

Navigate to http://localhost:3000/api/cats to see SwaggerUI for your cats:

OpenAPI (Swagger.md) - 图4

While http://localhost:3000/api/dogs will expose a SwaggerUI for your dogs:

OpenAPI (Swagger.md) - 图5

warning Notice You have to construct a SwaggerOptions with DocumentBuilder, run createDocument() against newly constructed options then immediately “serve” it with setup() before you can start working on a second SwaggerOptions for a second Swagger Specification. This specific order is to prevent Swagger configurations being overridden by different options.

Working with enums

To be able for SwaggerModule to identify an Enum, we have to manually set the enum property on @ApiModelProperty with an array of values.

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

UserRole enum can be defined as following:

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

warning Note The above usage can only be applied to a property as part of a model definition.

Enums can be used by itself with the @Query() parameter decorator in combination with the @ApiImplicitQuery() decorator.

  1. @ApiImplicitQuery({ name: 'role', enum: ['Admin', 'Moderator', 'User'] })
  2. async filterByRole(@Query('role') role: UserRole = UserRole.User) {
  3. // role returns: UserRole.Admin, UserRole.Moderator OR UserRole.User
  4. }

OpenAPI (Swagger.md) - 图6

warning Hint enum and isArray can also be used in combination in @ApiImplicitQuery()

With isArray set to true, the enum can be selected as a multi-select:

OpenAPI (Swagger.md) - 图7

Working with arrays

We have to manually indicate a type when the property is actually an array:

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

Simply put your type as the first element of an array (as shown above) or set an isArray property to true.

Tags

At the beginning, we created a cats tag (by making use of DocumentBuilder). In order to attach the controller to the specified tag, we need to use @ApiUseTags(...tags) decorator.

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

Responses

To define a custom HTTP response, we use @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. }

Same as common HTTP exceptions defined in Exception Filters section, Nest also provides a set of usable API responses that inherits from the core @ApiResponse decorator:

  • @ApiOkResponse()
  • @ApiCreatedResponse()
  • @ApiBadRequestResponse()
  • @ApiUnauthorizedResponse()
  • @ApiNotFoundResponse()
  • @ApiForbiddenResponse()
  • @ApiMethodNotAllowedResponse()
  • @ApiNotAcceptableResponse()
  • @ApiRequestTimeoutResponse()
  • @ApiConflictResponse()
  • @ApiGoneResponse()
  • @ApiPayloadTooLargeResponse()
  • @ApiUnsupportedMediaTypeResponse()
  • @ApiUnprocessableEntityResponse()
  • @ApiInternalServerErrorResponse()
  • @ApiNotImplementedResponse()
  • @ApiBadGatewayResponse()
  • @ApiServiceUnavailableResponse()
  • @ApiGatewayTimeoutResponse()

In addition to the available HTTP exceptions, Nest provides short-hand decorators for: HttpStatus.OK, HttpStatus.CREATED and HttpStatus.METHOD_NOT_ALLOWED

  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 the requests, one has to create a class and annotate all properties with the @ApiModelProperty() decorator.

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

Afterward, Cat model has to be used in combination with the type property of the response decorators.

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

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

OpenAPI (Swagger.md) - 图8

Authentication

You can enable the bearer authorization using addBearerAuth() method of the DocumentBuilder class. Then to restrict the chosen route or entire controller, use @ApiBearerAuth() decorator.

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

That’s how the OpenAPI documentation should look like now:

OpenAPI (Swagger.md) - 图9

File upload

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

  1. @UseInterceptors(FileInterceptor('file'))
  2. @ApiConsumes('multipart/form-data')
  3. @ApiImplicitFile({ name: 'file', required: true, description: 'List of cats' })
  4. uploadFile(@UploadedFile() file) {}

Decorators

All of the available OpenAPI decorators has an Api prefix to be clearly distinguishable from the core decorators. Below is a full list of the exported decorators with a defined use-level (where might be applied).

@ApiOperation() Method
@ApiResponse() Method / Controller
@ApiProduces() Method / Controller
@ApiConsumes() Method / Controller
@ApiBearerAuth() Method / Controller
@ApiOAuth2Auth() Method / Controller
@ApiImplicitBody() Method
@ApiImplicitParam() Method
@ApiImplicitQuery() Method
@ApiImplicitHeader() Method
@ApiImplicitFile() Method
@ApiExcludeEndpoint() Method
@ApiUseTags() Method / Controller
@ApiModelProperty() Model
@ApiModelPropertyOptional() Model

A working example is available here.