DEV Community

Željko Šević
Željko Šević

Posted on • Originally published at sevic.dev on

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.

Top comments (2)

Collapse
 
gabrielq7 profile image
Gabriel Aguiar Aquino

Great content

Collapse
 
zsevic profile image
Željko Šević

Thanks, I'm glad you find this article to be helpful