name: swagger-api-docs description: Tích hợp và sử dụng Swagger/OpenAPI cho NestJS API documentation. Bao gồm decorators, authentication setup, DTO documentation, và custom documentation patterns.
Swagger API Docs
Skill này tập trung vào việc tạo API documentation chuyên nghiệp sử dụng Swagger/OpenAPI trong NestJS.
Nguyên tắc bắt buộc
- Document tất cả public API endpoints.
- DTOs phải có đầy đủ type, validation rules, và examples.
- Authentication flows phải được document rõ ràng.
- Response schemas phải cover cả success và error cases.
Khi nào dùng
- Setup Swagger cho dự án mới.
- Document API endpoints và DTOs.
- Setup authentication trong Swagger UI.
- Tạo client SDKs từ OpenAPI spec.
Setup cơ bản
1. Bootstrap Swagger
// main.ts
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle("API Documentation")
.setDescription("API description")
.setVersion("1.0")
.addTag("users", "User management")
.addTag("auth", "Authentication")
.addBearerAuth(
{
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
name: "JWT",
description: "Enter JWT token",
in: "header",
},
"JWT-auth",
)
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api/docs", app, document);
await app.listen(3000);
}
2. Controller Documentation
@ApiTags("users")
@Controller("api/v1/users")
@UseGuards(JwtAuthGuard)
@ApiBearerAuth("JWT-auth")
export class UsersController {
@Post()
@ApiOperation({
summary: "Create new user",
description: "Creates a user with role and permissions",
})
@ApiCreatedResponse({
description: "User created successfully",
type: UserResponseDto,
})
@ApiConflictResponse({
description: "Email already exists",
type: ErrorResponseDto,
})
@ApiBadRequestResponse({
description: "Invalid input data",
type: ValidationErrorResponseDto,
})
async create(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
return this.usersService.create(createUserDto);
}
@Get(":id")
@ApiOperation({ summary: "Get user by ID" })
@ApiParam({
name: "id",
description: "User UUID",
example: "550e8400-e29b-41d4-a716-446655440000",
})
@ApiOkResponse({ description: "User found", type: UserResponseDto })
@ApiNotFoundResponse({ description: "User not found" })
async findOne(@Param("id") id: string): Promise<UserResponseDto> {
return this.usersService.findOne(id);
}
}
3. DTO Documentation
// Request DTO
export class CreateUserDto {
@ApiProperty({
description: "User email address",
example: "user@example.com",
format: "email",
})
@IsEmail()
email: string;
@ApiProperty({
description: "User full name",
example: "John Doe",
minLength: 2,
maxLength: 100,
})
@IsString()
@MinLength(2)
@MaxLength(100)
name: string;
@ApiProperty({
description: "User role",
enum: UserRole,
example: UserRole.USER,
default: UserRole.USER,
})
@IsEnum(UserRole)
role: UserRole;
@ApiProperty({
description: "User permissions",
type: [String],
example: ["users:read", "users:write"],
required: false,
})
@IsOptional()
@IsArray()
permissions?: string[];
}
// Response DTO
export class UserResponseDto {
@ApiProperty({
description: "User unique identifier",
example: "550e8400-e29b-41d4-a716-446655440000",
format: "uuid",
})
id: string;
@ApiProperty({
description: "User email",
example: "user@example.com",
})
email: string;
@ApiProperty({
description: "User name",
example: "John Doe",
})
name: string;
@ApiProperty({
description: "Creation timestamp",
example: "2024-01-15T10:30:00Z",
})
createdAt: Date;
}
4. Pagination Response
export class PaginationMetaDto {
@ApiProperty({ description: "Total items", example: 100 })
total: number;
@ApiProperty({ description: "Current page", example: 1 })
page: number;
@ApiProperty({ description: "Items per page", example: 20 })
limit: number;
@ApiProperty({ description: "Total pages", example: 5 })
totalPages: number;
}
export class PaginatedResponseDto<T> {
@ApiProperty({ isArray: true })
data: T[];
@ApiProperty({ type: PaginationMetaDto })
meta: PaginationMetaDto;
}
// Usage with generic
export const ApiPaginatedResponse = <TModel extends Type<any>>(
model: TModel,
) => {
return applyDecorators(
ApiOkResponse({
schema: {
allOf: [
{ $ref: getSchemaPath(PaginatedResponseDto) },
{
properties: {
data: {
type: "array",
items: { $ref: getSchemaPath(model) },
},
},
},
],
},
}),
);
};
5. File Upload Documentation
@Post('upload')
@ApiConsumes('multipart/form-data')
@ApiBody({
schema: {
type: 'object',
properties: {
file: {
type: 'string',
format: 'binary',
description: 'File to upload (max 5MB)',
},
description: {
type: 'string',
description: 'File description',
},
},
},
})
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile() file: Express.Multer.File,
@Body('description') description: string,
) {
// Handle upload
}
6. Error Response Documentation
export class ErrorResponseDto {
@ApiProperty({ example: 400 })
statusCode: number;
@ApiProperty({ example: "Bad Request" })
error: string;
@ApiProperty({ example: "Invalid input data" })
message: string;
@ApiProperty({ example: "2024-01-15T10:30:00Z" })
timestamp: string;
@ApiProperty({ example: "/api/v1/users" })
path: string;
}
export class ValidationErrorDto {
@ApiProperty({ example: 400 })
statusCode: number;
@ApiProperty({ example: "Validation failed" })
message: string;
@ApiProperty({
type: "object",
additionalProperties: { type: "array", items: { type: "string" } },
example: {
email: ["email must be a valid email address"],
name: ["name should not be empty"],
},
})
errors: Record<string, string[]>;
}
7. Custom Decorators
// Combine common decorators
export const ApiCommonResponses = () => {
return applyDecorators(
ApiUnauthorizedResponse({ description: 'Unauthorized - Invalid or missing token' }),
ApiForbiddenResponse({ description: 'Forbidden - Insufficient permissions' }),
ApiInternalServerErrorResponse({ description: 'Internal server error' }),
);
};
// Usage
@Post()
@ApiOperation({ summary: 'Create user' })
@ApiCreatedResponse({ type: UserResponseDto })
@ApiCommonResponses()
async create() {
// ...
}
8. Swagger Custom Options
SwaggerModule.setup("api/docs", app, document, {
swaggerOptions: {
persistAuthorization: true,
docExpansion: "none",
filter: true,
showRequestDuration: true,
},
customCssUrl:
"https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css",
customJs: [
"https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.js",
"https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.js",
],
});
Edge cases bắt buộc kiểm tra
- Circular dependencies: DTOs reference nhau.
- Generics: Generic DTOs không document đúng.
- Enums: Enum values phải hiển thị đúng trong UI.
- Optional fields:
required: falsecho optional properties. - Nested objects: Deep nested DTOs document đầy đủ.
- Union types: TypeScript union types cần
@ApiPropertyvớioneOf.
Best Practices
- Group APIs bằng
@ApiTags(). - Version APIs trong path hoặc header.
- Use
@ApiHideProperty()cho sensitive fields. - Document validation constraints từ class-validator.
- Add examples cho tất cả fields.
- Keep DTOs và documentation in sync.
Tiêu chí hoàn thành
- Swagger UI accessible tại
/api/docs. - Tất cả public endpoints được document.
- Authentication flow hoạt động trong Swagger UI.
- Request/Response examples đầy đủ và chính xác.
- Error responses documented rõ ràng.