Documenting REST APIs with OpenAPI specs (NestJS/Swagger)

zsevic

Željko Šević

Posted on March 16, 2023

Documenting REST APIs with OpenAPI specs (NestJS/Swagger)

OpenAPI is a language-agnostic specification for declaring API documentation for REST APIs. It contains the following information:

  • API information like title, description, version
  • endpoints definitions with request and response parameters
  • DTOs and security schemas
openapi: 3.0.0
paths:
  /users:
    post:
      operationId: UsersController_createUser
      summary: Create user
      description: Create a new user
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserDto'
      responses:
        '201':
          description: 'User is created'
info:
  title: nestjs-starter
  description: Minimal NestJS boilerplate
  version: 0.1.0
  contact: {}
tags: []
servers: []
components:
  securitySchemes:
    token:
      type: apiKey
      scheme: api_key
      in: header
      name: auth-token
  schemas:
    CreateUserDto:
      type: object
      properties:
        firstName:
          type: string
          example: tester
          description: first name of the user
      required:
        - firstName
Enter fullscreen mode Exit fullscreen mode

NestJS provides a Swagger plugin for generating the API docs.

Setup

Configure API documentation with the specified endpoint, like /api-docs, which shows the generated docs.

const SWAGGER_API_ENDPOINT = '/api-docs';
// ...

export const setupApiDocs = (app: INestApplication): void => {
  const options = new DocumentBuilder()
    .setTitle(SWAGGER_API_TITLE)
    .setDescription(SWAGGER_API_DESCRIPTION)
    .setVersion(SWAGGER_API_VERSION)
    .addSecurity('token', {
      type: 'apiKey',
      scheme: 'api_key',
      in: 'header',
      name: 'auth-token',
    })
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);

  SwaggerModule.setup(SWAGGER_API_ENDPOINT, app, document);
};
Enter fullscreen mode Exit fullscreen mode

Configure the plugin in the NestJS config file.

{
  "compilerOptions": {
    "plugins": ["@nestjs/swagger"]
  }
}
Enter fullscreen mode Exit fullscreen mode

JSON and YAML formats are generated at /api-docs-json and /api-docs-yaml endpoints, respectively.

Decorators

  • ApiTags groups endpoints
@ApiTags('users')
@Controller('users')
export class UsersController {
// ...
}
Enter fullscreen mode Exit fullscreen mode
  • ApiOperation provides more details like a summary and description of the endpoint
@ApiOperation({
  summary: 'Get user',
  description: 'Get user by id',
})
@Get(':id')
async getById(
  @Param('id', new ParseUUIDPipe()) id: string,
): Promise<UserDto> {
// ...
}
Enter fullscreen mode Exit fullscreen mode
  • ApiOperation can be used to mark an endpoint as deprecated
@ApiOperation({ deprecated: true })
Enter fullscreen mode Exit fullscreen mode
  • @ApiProperty and @ApiPropertyOptional should be used for request and response DTOs fields. Example and description values will be shown in the generated documentation.
export class CreateUserDto {
  @ApiProperty({ example: 'John', description: 'first name of the user' })
  // ...
  public firstName: string;

  @ApiPropertyOptional({ example: 'Doe', description: 'last name of the user' })
  // ...
  public lastName?: string;
}
Enter fullscreen mode Exit fullscreen mode
  • ApiHeader documents endpoint headers
@ApiHeader({
  name: 'correlation-id',
  required: false,
  description: 'unique id for correlated logs',
  example: '7ea2c7f7-8b46-475d-86f8-7aaaa9e4a35b',
})
@Get()
getHello(): string {
// ...
}
Enter fullscreen mode Exit fullscreen mode
  • ApiResponse specifies which responses are expected, like error responses. NestJS' Swagger package provides decorators for specific status codes like ApiBadRequestResponse.
// ...
@ApiResponse({ type: NotFoundException, status: HttpStatus.NOT_FOUND })
@ApiBadRequestResponse({ type: BadRequestException })
@Get(':id')
async getById(
  @Param('id', new ParseUUIDPipe()) id: string,
): Promise<UserDto> {
  return this.userService.findById(id);
}
// ...
Enter fullscreen mode Exit fullscreen mode
  • ApiSecurity('token') uses a custom-defined security strategy, token in this case. Other options are to use already defined strategies like ApiBearerAuth.
@ApiSecurity('token')
@Controller()
export class AppController {
// ...
}
// ...
@ApiBearerAuth()
@Controller()
export class AppController {
// ...
}
Enter fullscreen mode Exit fullscreen mode
  • ApiExcludeEndpoint and ApiExcludeController exclude one endpoint and the whole controller, respectively.
export class AppController {
  @ApiExcludeEndpoint()
  @Get()
  getHello(): string {
    // ...
  }
}
// ...
@ApiExcludeController()
@Controller()
export class AppController {
  // ...
}
Enter fullscreen mode Exit fullscreen mode
  • ApiBody with ApiExtraModels add an example for the request body
  const CreateUserDtoExample = {
    firstName: 'Tester',
  };

  @ApiExtraModels(CreateUserDto)
  @ApiBody({
    schema: {
      oneOf: refs(CreateUserDto),
      example: CreateUserDtoExample,
    },
  })
  @Post()
  async createUser(@Body() newUser: CreateUserDto): Promise<UserDto> {
    // ...
  }
Enter fullscreen mode Exit fullscreen mode

Importing API to Postman

Import JSON version of API docs as Postman API with Import → Link option (e.g., URL http://localhost:8081/api-docs-json). Imported API collection will be available in the APIs tab.

Boilerplate

Here is the link to the boilerplate I use for the development. It contains the examples mentioned above with more details.

💖 💪 🙅 🚩
zsevic
Željko Šević

Posted on March 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related