Swagger (OpenAPI) là chuẩn documentation phổ biến nhất cho REST API. NestJS có module @nestjs/swagger cho phép tự động generate Swagger UI từ code, không cần viết YAML thủ công.
1. Cài đặt
npm install --save @nestjs/swagger
2. Cấu hình Swagger trong main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('My API')
.setDescription('API documentation cho ứng dụng của tôi')
.setVersion('1.0')
.addBearerAuth( // Thêm nút "Authorize" cho JWT
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
'access-token',
)
.addTag('users', 'Quản lý người dùng')
.addTag('products', 'Quản lý sản phẩm')
.addTag('orders', 'Quản lý đơn hàng')
.build();
const document = SwaggerModule.createDocument(app, config);
// Chỉ expose docs trong môi trường không phải production
if (process.env.NODE_ENV !== 'production') {
SwaggerModule.setup('api-docs', app, document, {
swaggerOptions: {
persistAuthorization: true, // Giữ token khi reload trang
tagsSorter: 'alpha',
operationsSorter: 'alpha',
},
});
}
await app.listen(3000);
}
bootstrap();
Truy cập http://localhost:3000/api-docs.
3. Annotate DTO với Swagger decorators
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEmail, IsString, IsOptional, MinLength, IsEnum } from 'class-validator';
export enum UserRole {
ADMIN = 'admin',
USER = 'user',
MODERATOR = 'moderator',
}
export class CreateUserDto {
@ApiProperty({
description: 'Tên đầy đủ của người dùng',
example: 'Nguyen Van A',
minLength: 2,
maxLength: 50,
})
@IsString()
@MinLength(2)
name: string;
@ApiProperty({
description: 'Địa chỉ email',
example: 'nguyenvana@example.com',
format: 'email',
})
@IsEmail()
email: string;
@ApiProperty({
description: 'Mật khẩu (tối thiểu 8 ký tự)',
example: 'SecurePass123!',
minLength: 8,
})
@IsString()
@MinLength(8)
password: string;
@ApiPropertyOptional({
description: 'Vai trò người dùng',
enum: UserRole,
default: UserRole.USER,
})
@IsOptional()
@IsEnum(UserRole)
role?: UserRole = UserRole.USER;
@ApiPropertyOptional({
description: 'Số điện thoại',
example: '0912345678',
})
@IsOptional()
@IsString()
phone?: string;
}
// Response DTO
export class UserResponseDto {
@ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440000' })
id: string;
@ApiProperty({ example: 'Nguyen Van A' })
name: string;
@ApiProperty({ example: 'nguyenvana@example.com' })
email: string;
@ApiProperty({ enum: UserRole })
role: UserRole;
@ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
createdAt: Date;
}
4. Annotate Controller
import {
ApiTags, ApiOperation, ApiResponse, ApiBearerAuth,
ApiParam, ApiQuery, ApiBody, ApiConsumes,
} from '@nestjs/swagger';
@ApiTags('users')
@ApiBearerAuth('access-token') // Yêu cầu JWT cho toàn bộ controller
@Controller('users')
export class UsersController {
@Post()
@ApiOperation({ summary: 'Tạo người dùng mới' })
@ApiResponse({ status: 201, description: 'Tạo thành công', type: UserResponseDto })
@ApiResponse({ status: 400, description: 'Dữ liệu không hợp lệ' })
@ApiResponse({ status: 409, description: 'Email đã tồn tại' })
create(@Body() dto: CreateUserDto): Promise<UserResponseDto> {
return this.usersService.create(dto);
}
@Get()
@ApiOperation({ summary: 'Lấy danh sách người dùng' })
@ApiQuery({ name: 'page', required: false, type: Number, example: 1 })
@ApiQuery({ name: 'limit', required: false, type: Number, example: 10 })
@ApiQuery({ name: 'search', required: false, type: String })
@ApiResponse({ status: 200, description: 'Thành công', type: [UserResponseDto] })
findAll(@Query() query: PaginationDto) {
return this.usersService.findAll(query);
}
@Get(':id')
@ApiOperation({ summary: 'Lấy thông tin người dùng theo ID' })
@ApiParam({ name: 'id', description: 'UUID của người dùng', example: '550e8400-e29b-41d4-a716-446655440000' })
@ApiResponse({ status: 200, type: UserResponseDto })
@ApiResponse({ status: 404, description: 'Không tìm thấy người dùng' })
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post('avatar')
@ApiOperation({ summary: 'Upload ảnh đại diện' })
@ApiConsumes('multipart/form-data')
@ApiBody({
schema: {
type: 'object',
properties: {
file: { type: 'string', format: 'binary' },
},
},
})
@ApiResponse({ status: 200, description: 'Upload thành công' })
@UseInterceptors(FileInterceptor('file'))
uploadAvatar(@UploadedFile() file: Express.Multer.File) {
return this.usersService.uploadAvatar(file);
}
}
5. API Response chuẩn hóa
// Generic response wrapper
export class PaginatedResponseDto<T> {
@ApiProperty({ isArray: true })
items: T[];
@ApiProperty({ example: 100 })
total: number;
@ApiProperty({ example: 1 })
page: number;
@ApiProperty({ example: 10 })
limit: number;
@ApiProperty({ example: 10 })
totalPages: number;
}
// Dùng trong controller
@ApiResponse({
status: 200,
schema: {
allOf: [
{ properties: { items: { type: 'array', items: { $ref: '#/components/schemas/UserResponseDto' } } } },
{ properties: { total: { type: 'number' } } },
],
},
})
6. Export Swagger JSON để dùng với Postman
// Lưu swagger.json khi build
const document = SwaggerModule.createDocument(app, config);
writeFileSync('./swagger.json', JSON.stringify(document, null, 2));
Import file này vào Postman: Import → File → swagger.json — tất cả endpoints tự động có sẵn.
7. Kết luận
@nestjs/swagger biến comment code thành documentation tương tác:
@ApiProperty— mô tả field trong DTO, tự generate Swagger schema@ApiOperation/@ApiResponse— mô tả endpoint và responses@ApiTags— nhóm endpoints@ApiBearerAuth— thêm authentication vào UI- Swagger UI cho phép test API ngay trên trình duyệt, không cần Postman