Swagger — API dokumentatsiya, dekoratorlar, schema
Muammodan boshlaylik
Siz backend yozdingiz. Frontend dasturchi sizdan so'radi:
"POST /users ga nima yuborish kerak?
namerequired mi?roleqanday qiymatlar qabul qiladi? Xato kelsa qanday format bo'ladi?"
Siz har safar og'zaki tushuntirasiz, yoki Postman collection yubоrasiz, yoki hujjat yozasiz. U eskirib qoladi. Yangi dasturchi kelsa — yana tushuntirish kerak.
❌ Muammo:
Frontend: "login endpointi qanday ishlaydi?"
Backend: "kuting, aytib beraman..."
Bir hafta o'tdi. Endpoint o'zgardi.
Frontend: "nega ishlamayapti?"
Backend: "oh, men yangilagandim, aytishni unutdim..."
Bu muammoning yechimi — Swagger. Kod bilan birga yashaydi, doim yangi, dasturchi o'zi o'qiy oladi.
Swagger nima?
Swagger — API ni brauzerda vizual ko'rsatuvchi va sinab ko'rish imkonini beruvchi dokumentatsiya vositasi.
Siz yozasiz: Swagger ko'rsatadi:
─────────────────────────────────────────────────────
@ApiOperation(...) ┌─────────────────────────────┐
@ApiResponse(...) │ POST /users │
@ApiProperty(...) → │ Foydalanuvchi yaratish │
│ │
│ Request body: │
│ { name: string, │
│ email: string, │
│ password: string } │
│ │
│ [Try it out] ← ishlatib ko'rish│
└─────────────────────────────┘
Swagger ning asosi — OpenAPI Specification. Bu API ni tavsiflovchi standart format (JSON/YAML). Swagger shu formatni o'qib, vizual interfeys ko'rsatadi.
O'rnatish va sozlash
npm install @nestjs/swagger
main.ts da Swagger ni yoqamiz:
// 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);
// Swagger konfiguratsiyasi
const config = new DocumentBuilder()
.setTitle('Mening API m')
// Sahifa sarlavhasi — brauzer tab da ko'rinadi
.setDescription('Foydalanuvchilar va postlar boshqaruvi uchun REST API')
// Qisqacha tavsif
.setVersion('1.0')
// API versiyasi
.addBearerAuth()
// "Authorize" tugmasi — JWT token kiritish uchun
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
// 'api' — URL manzil: http://localhost:3000/api
await app.listen(3000);
}
bootstrap();
Ishga tushiring va brauzerda oching:
http://localhost:3000/api ← Swagger UI (vizual interfeys)
http://localhost:3000/api-json ← OpenAPI JSON (mashina uchun)
Swagger interfeysi qanday ko'rinadi
┌─────────────────────────────────────────────────────────────┐
│ Mening API m [Authorize 🔒] │
│ Foydalanuvchilar va postlar boshqaruvi uchun REST API │
│ │
│ ▼ users │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ POST /users Foydalanuvchi yaratish │ │
│ │ GET /users Barcha foydalanuvchilar │ │
│ │ GET /users/{id} ID bo'yicha olish │ │
│ │ PATCH /users/{id} Yangilash │ │
│ │ DELETE /users/{id} O'chirish │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ▼ auth │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ POST /auth/login Tizimga kirish │ │
│ │ POST /auth/register Ro'yxatdan o'tish │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Har bir endpointni bosganingizda — request body, parametrlar, response misollari ko'rsatiladi. "Try it out" bilan to'g'ridan-to'g'ri so'rov yuborish mumkin.
Dekoratorlar — qaysi biri nima qiladi
Swagger dekoratorlari ikkita joyda ishlatiladi:
Controller va metodlarda: DTO larda (class da):
────────────────────────────────────────────────────
@ApiTags(...) @ApiProperty(...)
@ApiOperation(...) @ApiPropertyOptional(...)
@ApiResponse(...)
@ApiBearerAuth()
@ApiParam(...)
@ApiQuery(...)
@ApiBody(...)
Har birini ko'ramiz.
DTO da @ApiProperty
@ApiProperty — DTO dagi har bir maydon haqida Swagger ga ma'lumot beradi. Bu eng ko'p ishlatiladigan dekorator.
// users/dto/create-user.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsEmail, IsEnum, MinLength, IsOptional } from 'class-validator';
export enum UserRole {
USER = 'user',
ADMIN = 'admin',
MODERATOR = 'moderator',
}
export class CreateUserDto {
@ApiProperty({
description: 'Foydalanuvchining to\'liq ismi',
example: 'Ali Valiyev',
minLength: 2,
maxLength: 100,
})
@IsString()
@MinLength(2)
name: string;
@ApiProperty({
description: 'Elektron pochta manzili',
example: 'ali@example.com',
format: 'email',
// format — Swagger da vizual belgi qo'shadi
})
@IsEmail()
email: string;
@ApiProperty({
description: 'Parol — kamida 8 ta belgi',
example: 'Secret123!',
minLength: 8,
format: 'password',
// format: 'password' — Swagger da yashirilgan maydon
})
@IsString()
@MinLength(8)
password: string;
@ApiPropertyOptional({
// ApiPropertyOptional = ApiProperty({ required: false })
description: 'Foydalanuvchi roli',
enum: UserRole,
default: UserRole.USER,
example: UserRole.USER,
})
@IsEnum(UserRole)
@IsOptional()
role?: UserRole;
}
Swagger da bu shunday ko'rinadi:
Request body:
┌─────────────────────────────────────────────────┐
│ { │
│ "name": "Ali Valiyev", ← example │
│ "email": "ali@example.com", ← example │
│ "password": "Secret123!", ← example │
│ "role": "user" ← optional │
│ } │
│ │
│ name* string Foydalanuvchining to'liq ismi │
│ email* string Elektron pochta │
│ password* string Parol — kamida 8 ta belgi │
│ role string Foydalanuvchi roli │
│ Enum: user, admin, moderator │
└─────────────────────────────────────────────────┘
* — required maydonlar. role ixtiyoriy — yulduzcha yo'q.
Controller da dekoratorlar
@ApiTags — guruh nomi
// users/users.controller.ts
import {
Controller, Get, Post, Body, Param, Delete, Patch, Query,
} from '@nestjs/common';
import {
ApiTags, ApiOperation, ApiResponse, ApiParam,
ApiQuery, ApiBearerAuth, ApiBody,
} from '@nestjs/swagger';
@ApiTags('users')
// Swagger da "users" guruhiga kiradi
// Bir controller — bir tag (odatda)
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@ApiOperation — endpoint tavsifi
@Post()
@ApiOperation({
summary: 'Yangi foydalanuvchi yaratish',
// summary — qisqa, bir qatorda tavsif
description: `
Yangi foydalanuvchi ro'yxatdan o'tkazadi.
Email unikal bo'lishi kerak.
Parol avtomatik hash qilinadi.
`,
// description — batafsil tavsif, markdown qo'llab-quvvatlanadi
})
@ApiResponse — javob holatlari
@ApiResponse({
status: 201,
description: 'Foydalanuvchi muvaffaqiyatli yaratildi',
type: User,
// type — javob qanday ko'rinishda bo'lishi (Entity yoki DTO)
})
@ApiResponse({
status: 400,
description: 'Noto\'g\'ri ma\'lumotlar — validatsiya xatosi',
})
@ApiResponse({
status: 409,
description: 'Bu email allaqachon ro\'yxatdan o\'tgan',
})
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@ApiParam — URL parametrlar
@ApiParam({
name: 'id',
description: 'Foydalanuvchi ID si',
type: Number,
example: 1,
})
@ApiResponse({ status: 200, description: 'Topildi', type: User })
@ApiResponse({ status: 404, description: 'Topilmadi' })
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@ApiQuery — query parametrlar
@ApiQuery({
name: 'page',
required: false,
type: Number,
description: 'Sahifa raqami',
example: 1,
})
@ApiQuery({
name: 'limit',
required: false,
type: Number,
description: 'Bir sahifadagi yozuvlar soni',
example: 10,
})
@ApiQuery({
name: 'search',
required: false,
type: String,
description: 'Ism yoki email bo\'yicha qidiruv',
example: 'ali',
})
@Get()
findAll(
@Query('page') page: number,
@Query('limit') limit: number,
@Query('search') search: string,
) {
return this.usersService.findAll({ page, limit, search });
}
To'liq controller — hamma bir joyda
// users/users.controller.ts
import { Controller, Get, Post, Body, Param, Patch, Delete, Query, ParseIntPipe, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery, ApiBearerAuth } from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
// ─── CREATE ──────────────────────────────────────────
@Post()
@ApiOperation({ summary: 'Yangi foydalanuvchi yaratish' })
@ApiResponse({ status: 201, description: 'Yaratildi' })
@ApiResponse({ status: 400, description: 'Validatsiya xatosi' })
@ApiResponse({ status: 409, description: 'Email band' })
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
// ─── READ ALL ─────────────────────────────────────────
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
// @ApiBearerAuth() — bu endpoint token talab qiladi
// Swagger da 🔒 belgi ko'rinadi
@ApiOperation({ summary: 'Barcha foydalanuvchilar ro\'yxati' })
@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: 'Muvaffaqiyatli' })
@ApiResponse({ status: 401, description: 'Token yo\'q yoki yaroqsiz' })
findAll(
@Query('page') page = 1,
@Query('limit') limit = 10,
@Query('search') search?: string,
) {
return this.usersService.findAll({ page, limit, search });
}
// ─── READ ONE ─────────────────────────────────────────
@Get(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'ID bo\'yicha foydalanuvchi' })
@ApiParam({ name: 'id', type: Number, example: 1 })
@ApiResponse({ status: 200, description: 'Topildi' })
@ApiResponse({ status: 404, description: 'Topilmadi' })
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
// ─── UPDATE ──────────────────────────────────────────
@Patch(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Foydalanuvchini yangilash' })
@ApiParam({ name: 'id', type: Number, example: 1 })
@ApiResponse({ status: 200, description: 'Yangilandi' })
@ApiResponse({ status: 404, description: 'Topilmadi' })
update(
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateUserDto,
) {
return this.usersService.update(id, dto);
}
// ─── DELETE ──────────────────────────────────────────
@Delete(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Foydalanuvchini o\'chirish' })
@ApiParam({ name: 'id', type: Number, example: 1 })
@ApiResponse({ status: 204, description: 'O\'chirildi' })
@ApiResponse({ status: 404, description: 'Topilmadi' })
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
Response schema — @ApiProperty Entity da
Swagger javobda qanday ma'lumot kelishini ko'rsatish uchun Entity yoki Response DTO ga ham @ApiProperty qo'shiladi:
// users/entities/user.entity.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class User {
@ApiProperty({ example: 1, description: 'Unikal identifikator' })
id: number;
@ApiProperty({ example: 'Ali Valiyev' })
name: string;
@ApiProperty({ example: 'ali@example.com' })
email: string;
@ApiProperty({ enum: UserRole, example: UserRole.USER })
role: UserRole;
@ApiProperty({ example: true })
isActive: boolean;
@ApiProperty({ example: '2026-01-01T00:00:00.000Z' })
createdAt: Date;
@ApiProperty({ example: '2026-01-01T00:00:00.000Z' })
updatedAt: Date;
// password — Swagger da ko'rsatilmaydi
// @ApiProperty yo'q — Swagger bu maydonni bilmaydi
}
Response DTO dan foydalanish (Entity o'rniga):
// users/dto/user-response.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class UserResponseDto {
@Expose()
@ApiProperty({ example: 1 })
id: number;
@Expose()
@ApiProperty({ example: 'Ali Valiyev' })
name: string;
@Expose()
@ApiProperty({ example: 'ali@example.com' })
email: string;
@Expose()
@ApiProperty({ enum: UserRole })
role: UserRole;
// password — @Expose() yo'q, javobda kelmaydi
}
Controller da type bilan bog'lash:
@ApiResponse({
status: 201,
description: 'Foydalanuvchi yaratildi',
type: UserResponseDto,
// Swagger "201 da shu ko'rinishda javob keladi" deb ko'rsatadi
})
@Post()
create(@Body() dto: CreateUserDto) { ... }
Sahifalash uchun wrapper DTO
// common/dto/paginated.dto.ts
import { ApiProperty } from '@nestjs/swagger';
export class PaginatedDto<T> {
@ApiProperty({ example: 42 })
total: number;
@ApiProperty({ example: 1 })
page: number;
@ApiProperty({ example: 5 })
lastPage: number;
// data — generic bo'lgani uchun alohida ko'rsatiladi
data: T[];
}
// Konkret tip uchun:
export class PaginatedUsersDto extends PaginatedDto<UserResponseDto> {
@ApiProperty({ type: [UserResponseDto] })
// [UserResponseDto] — massiv ekanligini ko'rsatadi
declare data: UserResponseDto[];
}
@ApiResponse({
status: 200,
type: PaginatedUsersDto,
})
@Get()
findAll(...) { ... }
Auth endpointlari
// auth/dto/login.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsString, MinLength } from 'class-validator';
export class LoginDto {
@ApiProperty({ example: 'ali@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: 'Secret123!', format: 'password' })
@IsString()
@MinLength(8)
password: string;
}
// auth/dto/token-response.dto.ts
export class TokenResponseDto {
@ApiProperty({
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
description: 'JWT access token — 15 daqiqa amal qiladi',
})
accessToken: string;
@ApiProperty({
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
description: 'Refresh token — 7 kun amal qiladi',
})
refreshToken: string;
}
// auth/auth.controller.ts
@ApiTags('auth')
@Controller('auth')
export class AuthController {
@Post('login')
@ApiOperation({ summary: 'Tizimga kirish' })
@ApiResponse({ status: 200, type: TokenResponseDto,
description: 'Access va refresh tokenlar qaytariladi' })
@ApiResponse({ status: 401,
description: 'Email yoki parol noto\'g\'ri' })
login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
@Post('refresh')
@ApiBearerAuth()
@ApiOperation({ summary: 'Access tokenni yangilash' })
@ApiResponse({ status: 200, type: TokenResponseDto })
@ApiResponse({ status: 401, description: 'Refresh token yaroqsiz' })
refresh(@Body() dto: RefreshTokenDto) {
return this.authService.refresh(dto.refreshToken);
}
}
JWT token bilan Swagger da ishlash
main.ts da addBearerAuth() qo'shilgan bo'lsa, Swagger da "Authorize" tugmasi paydo bo'ladi:
Swagger interfeysi:
┌──────────────────────────────────────────┐
│ [Authorize 🔒] ← shu tugmani bosing │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Available authorizations │
│ │
│ BearerAuth (http, Bearer) │
│ Value: eyJhbGc... ← tokenni yozing │
│ [Authorize] │
└──────────────────────────────────────────┘
Token kiritilgandan keyin @ApiBearerAuth() belgilangan barcha endpointlar avtomatik Authorization: Bearer <token> header bilan so'rov yuboradi.
Muhim sozlamalar
Production da Swagger yoqmaslik
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Faqat development da yoqiladi
if (process.env.NODE_ENV !== 'production') {
const config = new DocumentBuilder()
.setTitle('API')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
}
await app.listen(3000);
}
Nima uchun? Swagger UI orqali API strukturasi ochiq ko'rinadi — production da bu xavfli bo'lishi mumkin.
CLI plugin — @ApiProperty ni avtomatik
@nestjs/swagger ning CLI plugin i @ApiProperty dekoratorlarini DTO larga avtomatik qo'shadi. Qo'lda yozishning keragi yo'q:
// nest-cli.json
{
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
Plugin bilan DTO oddiy bo'ladi:
// Plugin bilan — @ApiProperty yozmasdan ham Swagger tushunadoi
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string; // ← @ApiProperty({ type: String }) avtomatik
@IsEmail()
email: string;
@IsEnum(UserRole)
@IsOptional()
role?: UserRole; // ← @ApiPropertyOptional({ enum: UserRole }) avtomatik
}
💡 Plugin
class-validatordekoratorlarini o'qib, tip va validatsiya qoidalarini avtomatik Swagger ga uzatadi. Juda vaqt tejaydi.
Tez-tez uchraydigan xatolar
@ApiProperty yozilmagan — Swagger da maydon ko'rinmaydi
// ❌ Yomon — Swagger bu maydonni bilmaydi
export class CreateUserDto {
name: string;
}
// ✅ To'g'ri
export class CreateUserDto {
@ApiProperty({ example: 'Ali' })
name: string;
}
type ko'rsatilmagan — Swagger "object" deydi
// ❌ Yomon
@ApiResponse({ status: 200, description: 'OK' })
// ✅ To'g'ri — Swagger javob qanday ko'rinishini biladi
@ApiResponse({ status: 200, type: UserResponseDto })
@ApiBearerAuth() bor, lekin addBearerAuth() yo'q
// ❌ Controller da @ApiBearerAuth() bor, lekin main.ts da:
const config = new DocumentBuilder()
.setTitle('API')
// addBearerAuth() unutilgan — Authorize tugmasi chiqmaydi
// ✅ To'g'ri
const config = new DocumentBuilder()
.setTitle('API')
.addBearerAuth() // ← bu bo'lishi shart
.build();
Massiv type noto'g'ri ko'rsatilgan
// ❌ Yomon — massiv ekanini bilmaydi
@ApiProperty({ type: UserResponseDto })
users: UserResponseDto[];
// ✅ To'g'ri — [] bilan massiv ekanini ko'rsatadi
@ApiProperty({ type: [UserResponseDto] })
users: UserResponseDto[];
Vazifalar
1-vazifa — Amaliy:
PostsController va CreatePostDto ga Swagger dekoratorlari qo'shing. title (required), content (required), published (optional, default: false) maydonlari bo'lsin. CRUD uchun @ApiOperation, @ApiResponse, @ApiParam yozing.
2-vazifa — JWT:
main.ts da addBearerAuth() qo'shing. Auth kerak bo'lgan endpointlarga @ApiBearerAuth() qo'shing. Brauzerda Swagger oching, token kiritib, himoyalangan endpointni sinab ko'ring.
3-vazifa — O'ylash:
CLI plugin ishlatganda va ishlatmaganda nima farq bor? Qaysi holatlarda @ApiProperty ni baribir qo'lda yozish kerak bo'ladi?
Xulosa
Swagger nima uchun kerak: Kod bilan birga yashaydi, doim yangi. Frontend dasturchi, test qiluvchi, yangi dasturchi — hamma o'zi o'rganishi mumkin.
Asosiy dekoratorlar:
| Dekorator | Qayerda | Nima qiladi |
|---|---|---|
@ApiTags('nom') |
Controller | Guruhga ajratadi |
@ApiOperation({...}) |
Metod | Endpoint tavsifi |
@ApiResponse({...}) |
Metod | Javob holati |
@ApiParam({...}) |
Metod | URL parametr |
@ApiQuery({...}) |
Metod | Query parametr |
@ApiBearerAuth() |
Metod/Class | 🔒 token kerak |
@ApiProperty({...}) |
DTO/Entity maydon | Maydon tavsifi |
@ApiPropertyOptional({...}) |
DTO maydon | Ixtiyoriy maydon |
Muhim qoidalar:
- Production da Swagger o'chirib qo'ying
- CLI plugin —
@ApiPropertyni avtomatik qo'shadi type: [DTO]— massiv uchunaddBearerAuth()+@ApiBearerAuth()— ikkalasi birga
Keyingi dars
53-dars: Jest Framework — Testing asoslari