OpenAPI for your REST APIs in NestJS
Setup Swagger to generate an OpenAPI documentation for your REST endpoints.
- Authors
- Marc Stammerjohann
- Published at
The OpenAPI documentation is a useful API playground for you to test or to share with other developers and for client generation tools (e.g ng-openapi-gen for Angular).
You'll find the source code in this repo.
Setup Swagger
Start with installing the Swagger dependencies.
# nest v9
npm install --save @nestjs/swagger class-transformer class-validator
# nest v8
# express
npm install --save @nestjs/swagger@5 swagger-ui-express class-transformer class-validator
# fastify
npm install --save @nestjs/swagger@5 fastify-swagger class-transformer class-validator
Now setup the initialization of Swagger in your main.ts
file.
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('NestJS Swagger')
.setDescription('API description')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
The setup is complete, start your Nest application npm run start:dev
and visit the Swagger endpoint localhost:3000/api.
Swagger API will be available at the path you provide in SwaggerModule.setup('api',...)
at http://localhost:3000/api
. Access the JSON file by opening http://localhost:3000/api-json
for express and http://localhost:3000/api/json
for fastify.
Generate in the next step CRUD endpoints for a resource like users or products and add type definitions for Swagger.
Generate REST resource
Use the Nest CLI to generate the boilerplate the resource for users.
nest generate resource
# short
nest g res
# CLI prompts
? What name would you like to use for this resource (plural, e.g., "users")? users
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? (Y/n) y
You'll find a new users
directory under src
containing all the boilerplates for your REST endpoints - module, controller, service, entity and dto files.
Start again the Nest application and you should see the new users
endpoints in the Swagger API.
API decorators
Apply available decorators prefixed with API to expose the properties for .dto.ts
and .entity.ts
files and the responses for your CRUD endpoints.
Tags
Group your endpoints together by using @ApiTags(...tags)
at the controller level.
import { Controller } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
@Controller('users')
@ApiTags('users') // 👈 apply tags
export class UsersController {
...
}
Property
Let's add the following properties name
, email
, password
to the CreateUserDto
and mark name as optional.
export class CreateUserDto {
email: string;
password: string;
name?: string | null;
}
To expose those properties to the Swagger API use @ApiProperty(options)
at the property level and pass options like required
, default
, description
and more.
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty()
email: string;
@ApiProperty()
password: string;
@ApiProperty({ required: false, nullable: true })
name?: string | null;
}
Refresh the Swagger API and you should see the properties for the CreateUserDto
.
Also have a look at the UpdateUserDto
schema in Swagger. The same properties are shown but all of them are marked as optional. This is because of PartialType
also called Mapped types provided by Nest.
import { PartialType } from '@nestjs/swagger';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
PartialType
applies the same properties from CreateUserDto
but set to optional.
Response
Add the same properties as before to the user.entity.ts
and only expose name
and email
to Swagger.
import { ApiProperty } from '@nestjs/swagger';
export class User {
@ApiProperty()
email: string;
password: string;
@ApiProperty({ required: false, nullable: true })
name?: string | null;
}
Additionally, Swagger needs help to pick up the response type. Annotate your REST endpoints with the custom @ApiResponse()
specifying the status code and the response type or choose a short-hand API response (e.g. @ApiOkResponse()
, @ApiCreatedResponse()
, ...).
@ApiOkResponse
:GET
andDELETE
@ApiCreatedResponse
:POST
andPATCH
@ApiForbiddenResponse
: endpoint might throw forbidden (403
) exception
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';
@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@ApiCreatedResponse({ type: User })
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
@ApiOkResponse({ type: [User] }) // 👈 array notation
findAll() {
return this.usersService.findAll();
}
@Get(':id')
@ApiOkResponse({ type: User })
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Patch(':id')
@ApiCreatedResponse({ type: User })
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id')
@ApiOkResponse({ type: User })
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
When the response type is an array
, you must indicate it using the array bracket notation ([ ]
) around the type
or set isArray
to true
. GET /users
response is an array of User
annotation looks like this:
@ApiOkResponse({ type: [User] })
@ApiOkResponse({ type: User, isArray: true })
You'll see the endpoints with the new response type of User
.
Swagger CLI Plugin
Exposing the properties and responses to Swagger results in additional boilerplate. Nest commes with a Swagger CLI Plugin to reduce boilerplate in your .dto.ts
and .entity.ts
files. Enable the plugin in your nest-cli.json
file.
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
Before: User
entity, CreateUserDto
and UsersController
with boilerplate.
export class User {
@ApiProperty()
email: string;
password: string;
@ApiProperty({ required: false, nullable: true })
name?: string | null;
}
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty()
email: string;
@ApiProperty()
password: string;
@ApiProperty({ required: false, nullable: true })
name?: string | null;
}
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';
@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@ApiCreatedResponse({ type: User })
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
@ApiOkResponse({ type: [User] })
findAll() {
return this.usersService.findAll();
}
@Get(':id')
@ApiOkResponse({ type: User })
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Patch(':id')
@ApiCreatedResponse({ type: User })
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id')
@ApiOkResponse({ type: User })
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
After: CLI plugin enabled and without boilerplate. You need to add @ApiHideProperty
otherwise the plugin will also expose the password
property.
import { ApiHideProperty } from '@nestjs/swagger';
export class User {
email: string;
@ApiHideProperty()
password: string;
name?: string | null;
}
export class CreateUserDto {
email: string;
password: string;
name?: string | null;
}
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';
@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto): User {
return this.usersService.create(createUserDto);
}
@Get()
findAll(): User[] {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): User {
return this.usersService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto): User {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string): User {
return this.usersService.remove(+id);
}
}
Advanced Swagger Types
Check out the following posts for type-safe file uploads and download.
This allows you to directly test file upload and download in your Swagger documentation and is perfect for client generation tools to pick up the correct input values for the file(s).
Sponsor us
Did you find this post useful? We at notiz.dev write about our experiences developing Apps, Websites and APIs and develop Open Source tools. Your support would mean a lot to us 🙏. Receive a reward by sponsoring us on Patreon or start with a one-time donation on GitHub Sponsors.