Swagger — API dokumentatsiya, dekoratorlar, schema

~25 daq · NestJS · Dokumentatsiya va Testing

Muammodan boshlaylik

Siz backend yozdingiz. Frontend dasturchi sizdan so'radi:

"POST /users ga nima yuborish kerak? name required mi? role qanday 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-validator dekoratorlarini 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 — @ApiProperty ni avtomatik qo'shadi
  • type: [DTO] — massiv uchun
  • addBearerAuth() + @ApiBearerAuth() — ikkalasi birga

Keyingi dars

53-dars: Jest Framework — Testing asoslari