API documentation: Swagger
OpenAPI is a standard for describing an HTTP API: which addresses exist, what they accept and return. Swagger UI is a web page that displays such a description and lets you send requests right from the browser. The @nestjs/swagger package builds the description from your controllers and DTOs, so the documentation does not have to be written separately.
Installation and setup
npm i @nestjs/swagger// src/main.ts
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('Blog API')
.setDescription('Posts, comments and users')
.setVersion('1.0')
.addBearerAuth()
.build();
const documentFactory = () => SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, documentFactory);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();DocumentBuilder sets the title and version, and addBearerAuth() adds a field for the JWT. Open http://localhost:3000/docs — a page lists every route. The JSON description lives at /docs-json.
Describing DTO fields
Swagger sees the routes but does not know the DTO fields: TypeScript types disappear at compile time. Fields are described with the @ApiProperty() decorator:
// src/posts/dto/create-post.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
export class CreatePostDto {
@ApiProperty({ example: 'Hello, Nest', maxLength: 120 })
@IsString()
@IsNotEmpty()
@MaxLength(120)
title: string;
@ApiProperty({ example: 'The text of the first post' })
@IsString()
@IsNotEmpty()
content: string;
}Now the documentation for POST /posts shows a sample request body. Optional fields are marked with @ApiPropertyOptional().
In UpdatePostDto replace the PartialType import with import { PartialType } from '@nestjs/swagger' — this version carries the field descriptions over too.
Groups and protection
// src/posts/posts.controller.ts
import { Controller } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('posts')
@ApiBearerAuth()
@Controller('posts')
export class PostsController {}@ApiTags() groups routes on the page, and @ApiBearerAuth() shows that they need a token. Click the Authorize button at the top of the page, paste a token — and requests from the documentation are sent with it.
Useful decorators
| Decorator | What it describes |
|---|---|
@ApiProperty() | A DTO or entity field |
@ApiTags('posts') | A group of routes |
@ApiBearerAuth() | A JWT is required |
@ApiOperation({ summary: '...' }) | A short route description |
@ApiResponse({ status: 404 }) | A possible response |
To avoid writing @ApiProperty() on every field, you can enable the CLI plugin: add "plugins": ["@nestjs/swagger"] to the compilerOptions section of nest-cli.json, and it describes fields from their TypeScript types.
Common mistake
The documentation shows an empty request body schema even though the DTO exists. The reason is that the fields have no @ApiProperty() and the plugin is not enabled. Swagger does not read class-validator decorators: validation rules and documentation are different things, and you need both.