Documenting REST APIs with OpenAPI specs (NestJS/Swagger)
Željko Šević
Posted on March 16, 2023
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
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);
};
Configure the plugin in the NestJS config file.
{
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
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 {
// ...
}
-
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> {
// ...
}
-
ApiOperation
can be used to mark an endpoint as deprecated
@ApiOperation({ deprecated: true })
-
@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;
}
-
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 {
// ...
}
-
ApiResponse
specifies which responses are expected, like error responses. NestJS' Swagger package provides decorators for specific status codes likeApiBadRequestResponse
.
// ...
@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);
}
// ...
-
ApiSecurity('token')
uses a custom-defined security strategy,token
in this case. Other options are to use already defined strategies likeApiBearerAuth
.
@ApiSecurity('token')
@Controller()
export class AppController {
// ...
}
// ...
@ApiBearerAuth()
@Controller()
export class AppController {
// ...
}
-
ApiExcludeEndpoint
andApiExcludeController
exclude one endpoint and the whole controller, respectively.
export class AppController {
@ApiExcludeEndpoint()
@Get()
getHello(): string {
// ...
}
}
// ...
@ApiExcludeController()
@Controller()
export class AppController {
// ...
}
-
ApiBody
withApiExtraModels
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> {
// ...
}
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.
Posted on March 16, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.