On this page
API documentation with Swagger and OpenAPI
Swagger and OpenAPI
Swagger (now called the OpenAPI specification) is the industry standard for documenting REST APIs. NestJS provides the @nestjs/swagger package which integrates deeply with the framework — it reads your decorators, DTOs, and type information to generate interactive API documentation automatically.
Install the package:
npm install @nestjs/swaggerSetting up Swagger
Initialize Swagger in main.ts using DocumentBuilder and SwaggerModule:
const config = new DocumentBuilder()
.setTitle('Bookstore API')
.setVersion('1.0')
.setDescription('The bookstore API description')
.setContact('David Morales', 'https://moralesvegadavid.com', '[email protected]')
.setLicense('MIT', 'https://opensource.org/licenses/MIT')
.addServer('http://localhost:3000', 'Development server')
.addServer('https://api.bookstore.com', 'Production server')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);Visit http://localhost:3000/api/docs to see the interactive UI.
Decorating controllers
@ApiTags
Group endpoints by tag:
@ApiTags('books')
@Controller('books')
export class BooksController {}@ApiOperation
Describe what a route does:
@Get(':id')
@ApiOperation({
summary: 'Get a book by ID',
description: 'Returns a single book with author and category details.',
})
findOne(@Param('id', ParseIntPipe) id: number) {}@ApiResponse
Document possible responses:
@Get(':id')
@ApiResponse({ status: 200, description: 'Book found', type: BookResponseDto })
@ApiResponse({ status: 404, description: 'Book not found' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
findOne(@Param('id', ParseIntPipe) id: number) {}@ApiParam and @ApiQuery
Document path and query parameters:
@Get(':id')
@ApiParam({ name: 'id', type: Number, description: 'Book ID', example: 1 })
findOne(@Param('id', ParseIntPipe) id: number) {}
@Get()
@ApiQuery({ name: 'page', type: Number, required: false, example: 1 })
@ApiQuery({ name: 'search', type: String, required: false, example: 'clean code' })
findAll() {}Decorating DTOs
@ApiProperty
Document DTO fields:
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateBookDto {
@ApiProperty({ example: 'Clean Code', maxLength: 200 })
title: string;
@ApiProperty({ example: 39.99, minimum: 0 })
price: number;
@ApiPropertyOptional({ example: 1 })
categoryId?: number;
}Enum properties
enum BookFormat { HARDCOVER = 'hardcover', PAPERBACK = 'paperback', EBOOK = 'ebook' }
export class CreateBookDto {
@ApiProperty({ enum: BookFormat, default: BookFormat.PAPERBACK })
format: BookFormat;
}Array properties
export class CreateBookDto {
@ApiProperty({ type: [String], example: ['fiction', 'adventure'] })
tags: string[];
}Nested DTOs
export class OrderDto {
@ApiProperty({ type: [OrderItemDto] })
@ValidateNested({ each: true })
@Type(() => OrderItemDto)
items: OrderItemDto[];
}Response DTOs
Define separate response DTOs that include computed fields:
export class BookResponseDto {
@ApiProperty({ example: 1 })
id: number;
@ApiProperty({ example: 'Clean Code' })
title: string;
@ApiProperty({ example: 39.99 })
price: number;
@ApiProperty({ type: AuthorSummaryDto })
author: AuthorSummaryDto;
@ApiProperty({ example: '2026-04-02T00:00:00.000Z' })
createdAt: string;
}Use ClassSerializerInterceptor with @Exclude() and @Expose() from class-transformer to control what properties are included in responses.
Authentication in Swagger UI
Configure the Authorize button for JWT:
const config = new DocumentBuilder()
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
name: 'Authorization',
in: 'header',
},
'access-token', // Reference name
)
.build();Apply to controllers that require authentication:
@ApiBearerAuth('access-token')
@Controller('books')
export class BooksController {}With persistAuthorization: true in the setup options, the Swagger UI remembers the token between page refreshes.
Swagger CLI plugin
Add the plugin to nest-cli.json to avoid manually decorating every DTO field:
{
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"introspectComments": true,
"classValidatorShim": true
}
}
]
}
}The plugin reads TypeScript types and JSDoc comments to generate @ApiProperty metadata automatically. You still add explicit @ApiProperty for examples and special cases, but the boilerplate is eliminated.
Exporting the OpenAPI document
Generate the OpenAPI JSON at build time for client generation:
// generate-swagger.ts (run as a separate script)
async function generateOpenApiSpec(): Promise<void> {
const app = await NestFactory.create(AppModule, { logger: false });
const config = new DocumentBuilder()
.setTitle('Bookstore API')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
writeFileSync('./openapi.json', JSON.stringify(document, null, 2));
await app.close();
}
generateOpenApiSpec();Then use orval or openapi-generator to generate a TypeScript client from openapi.json.
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({ whitelist: true, transform: true }),
);
// Only expose Swagger in non-production environments
if (process.env['NODE_ENV'] !== 'production') {
const config = new DocumentBuilder()
.setTitle('Bookstore API')
.setDescription('Complete REST API for the bookstore application')
.setVersion('1.0')
.addBearerAuth(
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
'access-token',
)
.addTag('books', 'Book management endpoints')
.addTag('authors', 'Author management endpoints')
.addTag('auth', 'Authentication and authorization')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document, {
swaggerOptions: { persistAuthorization: true },
});
}
await app.listen(3000);
}
bootstrap();
Sign in to track your progress