WisarWisar
Dasturlash kitobi/8-QISM — NestJS21 daqiqa

8.27-bob: API versioning

8-QISM — NestJS (chuqur) · 27-mavzu · Amaliy real mavzu


1. Kirish va motivatsiya

Endi API'ni vaqt o'tib o'zgartirish muammosini hal qiladigan mavzu — API versioning (versiyalash). Tasavvur qiling: sizning API'ngizdan mobil ilova (App Store/Play Market'da — siz yangilay olmaysiz, foydalanuvchi yangilaydi), tashqi hamkorlar (integratsiya qilgan), va veb sayt foydalanadi. Endi siz API javobini o'zgartirishingiz kerak (yangi maydon, boshqa format). Agar to'g'ridan o'zgartirsangiz — eski mobil ilovalar buziladi (minglab foydalanuvchi xato ko'radi). API versioning — bu muammoning yechimi: eski versiya (v1) ishlashda davom etadi, yangi (v2) qo'shiladi.

API — shartnoma (kontrakt): mijoz (frontend, mobil, hamkor) API qanday javob berishiga ishonadi. Bu shartnomani buzish (breaking change) — mijozlarni sindiradi. Lekin biznes o'sadi, API o'zgarishi kerak. Yechim: versiyalash — har versiya alohida shartnoma (v1 eski mijozlar uchun, v2 yangi imkoniyatlar uchun). Eski mijozlar o'z vaqtida ko'chadi (deprecation). Bu — ayniqsa public API (hamkorlar) va mobil ilova (sekin yangilanadi) bo'lganda kritik.

Bu bob: nega versioning (breaking change muammosi), breaking vs non-breaking o'zgarishlar, versiyalash usullari (URI /v1/, header, media type — qachon qaysi), NestJS versioning (built-in), versiyalar orasida kod ulashish, deprecation (eski versiyani to'xtatish — ogohlantirish), va best practices. Bu bob 5.7 (REST), 8.1 (controller), 8.5 (DTO) bilan bog'liq. API versioning — barqaror, uzoq yashaydigan API asosi.

O'xshatish: API versioning — mahsulotning yangi modelini chiqarish (eskisini to'xtatmasdan). Avtomobil zavodi yangi model (v2) chiqaradi, lekin eski model (v1) egalariga ehtiyot qismlar va xizmat davom etadi (birdan to'xtatsa — mijozlar g'azablanadi). Vaqt o'tib, eski modelni asta-sekin to'xtatadi (oldindan ogohlantirib — "v1 keyingi yil to'xtaydi, v2 ga o'ting" — deprecation). API ham shunday: yangi versiya qo'shiladi, eski ishlaydi, asta-sekin (ogohlantirish bilan) to'xtatiladi. Mijozlarni hurmat qilish.

Nega muhim?

  • Mijozni sindirmaslik — eski mobil/hamkor ishlashda davom (breaking change).
  • API rivojlanishi — yangi imkoniyat qo'shish (eskisini buzmasdan).
  • Public API/mobil — kritik (siz mijozni yangilay olmaysiz).
  • Professional API — barqaror shartnoma (ishonch).

2. Nazariya — chuqur tushuntirish

2.1. Nega versioning (breaking change muammosi)

text
  MUAMMO: API javobini o'zgartirish  eski mijozlar buziladi

  v1 javob:  { "name": "Ali", "phone": "+998..." }
  O'zgartirdingiz:  { "fullName": "Ali", "contact": { "phone": "..." } }
   Eski mobil ilova "name" kutadi  undefined  ilova buziladi!

  YECHIM — VERSIONING:
  /v1/users  eski format (eski mijozlar)
  /v2/users  yangi format (yangi mijozlar)
   ikkalasi ishlaydi (eski sindirilmaydi)

Nega versioning: API javobini o'zgartirsangiz (maydon nomi, struktura) — eski mijozlar (uni kutgan) buziladi. Mobil ilova — siz yangilay olmaysiz (foydalanuvchi App Store'dan yangilaydi — ko'pi eski versiyada qoladi). Yechim: versiyalash — v1 (eski format) ishlaydi, v2 (yangi) qo'shiladi. Ikkalasi parallel. Bu — API'ni rivojlantirish + mijozni hurmat qilish balansi.

2.2. Breaking vs non-breaking o'zgarishlar

text
  NON-BREAKING (versiya kerak EMAS — qo'shish mumkin):
   Yangi ixtiyoriy maydon qo'shish (javobga)
   Yangi endpoint qo'shish
   Yangi ixtiyoriy parametr (default bilan)

  BREAKING (versiya KERAK — eski mijozni buzadi):
   Maydon o'chirish/nomini o'zgartirish
   Javob strukturasini o'zgartirish
   Maydon turini o'zgartirish (string  object)
   Majburiy parametr qo'shish
   Xulq o'zgarishi (status kod, validatsiya qoidasi)

   Faqat BREAKING o'zgarish uchun yangi versiya (har o'zgarishga emas)

Breaking vs non-breaking (eng muhim farq): non-breaking (qo'shish — yangi ixtiyoriy maydon/endpoint/parametr — eski mijoz ta'sirlanmaydi versiya kerak emas); breaking (o'chirish/o'zgartirish — maydon, struktura, tur, majburiy parametr — eski mijoz buziladi yangi versiya). Faqat breaking uchun versiya (har o'zgarishga emas — versiya inflyatsiyasidan qoching). Iloji bo'lsa — non-breaking (qo'shish — versiyasiz). Bu — versiyalash strategiyasining asosi.

2.3. Versiyalash usullari (qachon qaysi)

text
  ┌──────────────┬──────────────────────────────────────────────┐
  │ URI          │ /v1/users, /v2/users                         │
  │              │  Aniq, ko'rinadigan, oson test (brauzer)    │
  │              │  "toza REST emas" (resurs URL o'zgaradi)    │
  │              │  ENG KENG (amaliy, aniq)                     │
  ├──────────────┼──────────────────────────────────────────────┤
  │ Header       │ Accept-Version: 2 / X-API-Version: 2         │
  │              │  URL toza;  ko'rinmaydi (test qiyin)       │
  ├──────────────┼──────────────────────────────────────────────┤
  │ Media type   │ Accept: application/vnd.api.v2+json          │
  │              │  "toza REST";  murakkab                    │
  ├──────────────┼──────────────────────────────────────────────┤
  │ Query        │ /users?version=2                             │
  │              │  oddiy;  kesh/yo'naltirish muammosi        │
  └──────────────┴──────────────────────────────────────────────┘

Versiyalash usullari: URI (/v1/users — eng keng, aniq, ko'rinadigan, oson test — "toza REST emas" lekin amaliy); header (X-API-Version — URL toza, lekin ko'rinmaydi); media type (Accept: ...v2+json — toza REST, murakkab); query (?version=2 — oddiy, lekin kesh muammosi). Ko'p loyiha URI versioning (aniq, oddiy — eng keng). Public API ba'zan header. Tanlov — aniqlik vs tozalik.

2.4. NestJS versioning (built-in)

typescript
// main.ts — versioning yoqish
import { VersioningType } from "@nestjs/common";

app.enableVersioning({
  type: VersioningType.URI,                           // /v1/, /v2/ (2.3)
  defaultVersion: "1",
});

// Controller — versiya bilan
@Controller({ path: "users", version: "1" })
export class UsersV1Controller {
  @Get() hammasi() { return this.service.eskiFormat(); }
}

@Controller({ path: "users", version: "2" })          // /v2/users
export class UsersV2Controller {
  @Get() hammasi() { return this.service.yangiFormat(); }
}

NestJS versioning (built-in): enableVersioning({ type: VersioningType.URI }) — URI/HEADER/MEDIA_TYPE qo'llab-quvvatlanadi. Controller @Controller({ path, version }) yoki metod @Version("2"). /v1/users V1 controller, /v2/users V2. defaultVersion (versiyasiz so'rov uchun). NestJS — versiyalashni framework darajasida (toza, oson). Bu — NestJS'ning kuchi (qo'lda routing emas).

NestJS to'rtta versiyalash turini qo'llab-quvvatlaydi (VersioningType). Har biri — turli mijoz talabiga mos:

typescript
import { VersioningType } from "@nestjs/common";

// (1) URI — /v1/users (eng keng)
app.enableVersioning({ type: VersioningType.URI, prefix: "v", defaultVersion: "1" });

// (2) HEADER — maxsus header'da versiya (URL bir xil qoladi)
app.enableVersioning({ type: VersioningType.HEADER, header: "X-API-Version" });
// Mijoz: GET /users + "X-API-Version: 2"

// (3) MEDIA_TYPE — Accept sarlavhasida versiya (toza REST — resurs URL o'zgarmaydi)
app.enableVersioning({ type: VersioningType.MEDIA_TYPE, key: "v=" });
// Mijoz: GET /users + "Accept: application/json;v=2"   key "v=" dan keyingi qiymat = versiya

// (4) CUSTOM — versiyani so'rovdan o'zingiz ajratib olasiz (extractor funksiya)
app.enableVersioning({
  type: VersioningType.CUSTOM,
  // So'rovdan versiyani qaytaradigan funksiya (masalan, subdomen yoki maxsus mantiq)
  extractor: (req: Record<string, any>): string | string[] => {
    const host = req.headers?.host ?? "";       // masalan, "v2.api.example.com"
    const match = host.match(/^v(\d+)\./);        // subdomen'dan versiya
    return match ? match[1] : "1";                // topilmasa — v1 (default)
  },
});

To'rt versiyalash turi: URI (VersioningType.URI) — /v1/users, eng keng, aniq, brauzerda ochiladi; HEADER (VersioningType.HEADER, header: "X-API-Version") — versiya sarlavhada, URL bir xil; MEDIA_TYPE (VersioningType.MEDIA_TYPE, key: "v=") — Accept: application/json;v=2, HTTP kontent-muzokarasiga mos "toza REST"; CUSTOM (VersioningType.CUSTOM, extractor) — versiyani so'rovdan o'zingiz ajratasiz (subdomen, murakkab qoida). Extractor string (bitta versiya) yoki string[] (bir nechta mos versiya — birinchi topilgani ustun) qaytaradi. Aksariyat loyiha URI'ni tanlaydi; MEDIA_TYPE/CUSTOM — maxsus talab bo'lganda.

2.5. Versiyalar orasida kod ulashish (DRY)

typescript
// Muammo: v1 va v2 ko'p kod takrorlaydi
// Yechim: umumiy service, faqat FARQNI versiyalash (DTO/transformer)

@Injectable()
export class UsersService {                           // umumiy biznes mantiq (bir marta)
  hammasi() { return this.repo.find(); }              // ichki domen format
}

// V1 — eski format (transformer)
@Controller({ path: "users", version: "1" })
export class UsersV1Controller {
  constructor(private service: UsersService) {}
  @Get() async hammasi() {
    const users = await this.service.hammasi();
    return users.map((u) => ({ name: u.ism, phone: u.telefon }));   // V1 format (8.5 Response DTO)
  }
}
// V2 — yangi format
@Controller({ path: "users", version: "2" })
export class UsersV2Controller {
  @Get() async hammasi() {
    const users = await this.service.hammasi();
    return users.map((u) => ({ fullName: u.ism, contact: { phone: u.telefon } }));   // V2
  }
}

Kod ulashish (DRY — eng muhim amaliy masala): v1/v2 ko'p kod takrorlaydi (xato). Yechim: umumiy service (biznes mantiq bir marta — 8.1), faqat farqni (javob formati — Response DTO/transformer — 8.5) versiyalash. Controller yupqa (faqat format). Versiyalash — butun ilovani emas, faqat o'zgargan qismni (controller/DTO). Aks holda ikki marta ko'p kod. Service qatlami versiyasiz.

2.6. Deprecation (eski versiyani to'xtatish)

text
  DEPRECATION — eski versiyani ASTA-SEKIN to'xtatish (birdan emas):

  1. E'lon: "v1 6 oydan keyin to'xtaydi, v2 ga o'ting" (oldindan!)
  2. Ogohlantirish header: Deprecation: true, Sunset: 2026-12-31
  3. Hujjat: v1 deprecated, v2 tavsiya
  4. Monitoring: v1 hali kim ishlatadi (kuzatish)
  5. To'xtatish: muddat o'tgach (mijozlar ko'chgach)

   Mijozga VAQT ber (mobil — oylar kerak); birdan to'xtatma

Deprecation (eski versiyani to'xtatish — to'g'ri usul): asta-sekin (birdan emas). E'lon (oldindan — "v1 6 oydan keyin to'xtaydi"), ogohlantirish header (Deprecation, Sunset sana), hujjat (v2 tavsiya), monitoring (v1 kim ishlatadi — 8.15), to'xtatish (mijozlar ko'chgach). Mijozga vaqt bering (mobil ilova — foydalanuvchi yangilashi oylar; hamkor — kod o'zgartirishi vaqt). Birdan to'xtatish — mijozni sindirish (ishonch yo'qoladi). Hurmatli deprecation.

2.7. Versiyalash strategiyasi (qancha versiya)

text
   KAM versiya saqla (har versiya — qo'shimcha yuk):
  - 1-2 faol versiya (joriy + oldingi)
  - Eskisini deprecate qil (yangi qo'shilganda)
  - "Versiyasiz qancha mumkin" (non-breaking — 2.2)

  Semantic versioning (API):
  - Major (v1v2): breaking change (yangi versiya)
  - Minor/patch: non-breaking (versiyasiz qo'shish)

   Versiya — oxirgi chora (avval non-breaking yo'lini izla)

Strategiya (qancha versiya): kam saqlang (1-2 faol — joriy + oldingi; har versiya — qo'llab-quvvatlash yuki). Yangi qo'shilganda — eng eskisini deprecate qiling 2.6-bob. Semantic versioning: major (v1v2 — breaking — yangi versiya); minor/patch (non-breaking — versiyasiz). Versiya — oxirgi chora (avval non-breaking yo'lini izlang — 2.2). Ko'p versiya — boshqarib bo'lmaydigan yuk (3-4 versiya — kabus). Minimal versiya — sog'lom API.

2.8. Migratsiya yo'li (v1v2 mijozlarni ko'chirish)

text
  MIGRATSIYA — mijozni v1'dan v2'ga ko'chirish rejasi:

  1. v2 chiqadi (v1 hali ishlaydi — ikkalasi parallel)
  2. v1 deprecate (Deprecation/Sunset header — 2.6)
  3. Migratsiya qo'llanmasi (v1v2 farqlar: qaysi maydon o'zgardi, mapping)
  4. Mijoz o'z tezligida ko'chadi (monitoring bilan kuzatasiz — Misol 8)
  5. v1 trafigi ~0  to'xtatish

  TRANSFORMER — v1v2 formatni bir joyda ushlash:
  domen (User)  V1Dto (eski) / V2Dto (yangi) — bitta domen, ikki xarita

Migratsiyada eng foydali vosita — transformer (mapper): domen modeli bitta bo'lib qoladi, har versiya uni o'z formatiga o'giradi. Bu — v1 va v2 orasida "ko'prik": v2 yangi maydon qo'shsa, v1 transformer eski shaklga qaytaradi (backward-compatible).

typescript
// Bir domen (User)  ikki versiya xaritasi (migratsiya ko'prigi)
export class UserMapper {
  // v2  v1 (eski mijoz uchun — yangi maydonlarni yashiradi/tekislaydi)
  static toV1(u: User) {
    return { name: u.ism, phone: u.telefon };            // eski tekis struktura
  }
  // domen  v2 (yangi mijoz uchun — boy struktura)
  static toV2(u: User) {
    return {
      fullName: u.ism,
      contact: { phone: u.telefon, email: u.email },      // ichma-ich (breaking)
      createdAt: u.createdAt.toISOString(),
    };
  }
}
//  yangi maydon domenga qo'shilsa, faqat toV2 o'zgaradi; toV1 barqaror (v1 mijoz buzilmaydi)

Migratsiya yo'li: v2 chiqqach mijozni majburan ko'chirmang — v1 parallel ishlaydi, deprecation header ogohlantiradi 2.6-bob, migratsiya qo'llanmasi (v1v2 maydon farqlari, mapping jadvali) beriladi, mijoz o'z tezligida o'tadi, monitoring (Misol 8) trafikni kuzatadi; v1 deyarli ishlatilmaganda to'xtatiladi. Transformer/mapper — migratsiyaning yuragi: domen bitta, har versiya alohida xarita; yangi maydon domenga qo'shilganda faqat yangi versiya xaritasi o'zgaradi, eski versiya xaritasi barqaror qoladi (v1 mijoz sindirilmaydi). Bu — non-breaking evolyutsiyani mumkin qiladi.

2.9. GraphQL'da versiyalash (REST'dan farqi)

text
  REST:  versiya (v1/v2) — butun endpoint takrorlanadi
  GraphQL: odatda VERSIYASIZ — schema evolyutsiyasi (bitta endpoint)

  GraphQL yondashuvi:
  - Yangi maydon QO'SHISH (non-breaking — mijoz so'ramasa, ta'sir yo'q)
  - Eski maydonni @deprecated bilan belgilash (o'chirmasdan)
  - Mijoz faqat kerakli maydonni so'raydi  versiya kamdan-kam kerak

GraphQL'da mijoz aynan qaysi maydonlarni so'rashini o'zi tanlaydi, shu bois yangi maydon qo'shish hech kimni buzmaydi (eski mijoz uni so'ramaydi). Shuning uchun GraphQL odatda REST kabi v1/v2 versiyalamaydi — o'rniga schema evolyutsiyasi va @deprecated direktivasi ishlatiladi (17-bob, GraphQL):

typescript
import { Field, ObjectType } from "@nestjs/graphql";

@ObjectType()
export class UserType {
  @Field()
  fullName: string;

  // Eski maydon — o'chirilmaydi, deprecated deb belgilanadi (mijozga ogohlantirish)
  @Field({ deprecationReason: "'fullName' ishlating; 'name' keyinchalik olib tashlanadi" })
  name: string;
}

GraphQL vs REST versiyalash: REST'da versiya butun endpoint'ni takrorlaydi (v1/v2); GraphQL'da mijoz kerakli maydonni o'zi tanlagani uchun yangi maydon qo'shish non-breaking — versiya kamdan-kam kerak. O'rniga schema evolyutsiyasi: yangi maydon qo'shiladi, eski maydon @deprecated (yoki deprecationReason) bilan belgilanadi (o'chirilmaydi), vosita (GraphiQL, kodgen) mijozga ogohlantirish ko'rsatadi. GraphQL versiyasini REST kabi boshqarmang — schema ichida evolyutsiya qiling (17-bob — GraphQL). Bu — GraphQL'ning REST'dan asosiy farqlaridan biri.

2.10. Best practices (versioning)

text
   Faqat BREAKING uchun versiya (non-breaking — versiyasiz — 2.2)
   URI versioning (aniq, eng keng — 2.3)
   Umumiy service + versiyalangan DTO/transformer (DRY — 2.5)
   Kam versiya (1-2 faol — 2.7)
   Deprecation (asta-sekin, ogohlantirish, vaqt — 2.6)
   Hujjatlash (Swagger — har versiya — 8.8)
   Monitoring (eski versiya kim ishlatadi — 8.15, 2.6)
   defaultVersion (versiyasiz so'rov — 2.4)
   Versiyalashni boshidan rejalashtir (keyin qo'shish qiyin)

3. Sintaksis — tez ma'lumotnoma

typescript
// Yoqish 2.4-bob: app.enableVersioning({ type: VersioningType.URI, defaultVersion: "1" })
// Turlar 2.4-bob: VersioningType.URI | HEADER | MEDIA_TYPE | CUSTOM
//   HEADER:     { type: ..., header: "X-API-Version" }
//   MEDIA_TYPE: { type: ..., key: "v=" }              // Accept: application/json;v=2
//   CUSTOM:     { type: ..., extractor: (req) => "2" } // versiyani o'zingiz ajratasiz
// Controller 2.4-bob: @Controller({ path: "users", version: "2" })
// Metod: @Version("2")  bitta(...) {}
// Bir nechta versiya: @Version(["1", "2"])
// Neutral (versiyasiz): version: VERSION_NEUTRAL       // barcha versiyada

4. Batafsil kod namunalari

Misol 1 — Versioning setup (2.4)

typescript
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableVersioning({
    type: VersioningType.URI,                         // /v1/, /v2/
    defaultVersion: "1",                              // versiyasiz  v1
    prefix: "v",                                       // /v1/ (default "v")
  });
  await app.listen(3000);
}
// GET /v1/users  V1; GET /v2/users  V2; GET /users  v1 (default)

Misol 2 — Versiyalangan controller (umumiy service — 2.5)

typescript
// Umumiy service (biznes mantiq — bir marta — 8.1)
@Injectable()
export class UsersService {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}
  async hammasi(): Promise<User[]> { return this.repo.find(); }
  async bitta(id: string): Promise<User> {
    const u = await this.repo.findOneBy({ id });
    if (!u) throw new NotFoundException();
    return u;
  }
}

// V1 controller — eski format
@Controller({ path: "users", version: "1" })
export class UsersV1Controller {
  constructor(private service: UsersService) {}
  @Get()
  async hammasi() {
    const users = await this.service.hammasi();
    return users.map((u) => UserV1Dto.from(u));       // eski format (8.5)
  }
}

// V2 controller — yangi format
@Controller({ path: "users", version: "2" })
export class UsersV2Controller {
  constructor(private service: UsersService) {}        // SHU service (DRY)
  @Get()
  async hammasi() {
    const users = await this.service.hammasi();
    return users.map((u) => UserV2Dto.from(u));       // yangi format
  }
}

Misol 3 — Versiyalangan Response DTO (format farqi — 2.5)

typescript
// V1 format (eski)
export class UserV1Dto {
  name: string;
  phone: string;
  static from(u: User): UserV1Dto {
    return { name: u.ism, phone: u.telefon };          // eski struktura
  }
}

// V2 format (yangi — breaking change)
export class UserV2Dto {
  fullName: string;
  contact: { phone: string; email: string };
  createdAt: string;
  static from(u: User): UserV2Dto {
    return {
      fullName: u.ism,
      contact: { phone: u.telefon, email: u.email },   // yangi struktura (breaking)
      createdAt: u.createdAt.toISOString(),
    };
  }
}
//  bir domen (User), ikki format (versiya) — transformer (DRY)

Misol 4 — Metod darajasida versiya (aralash — 2.4)

typescript
// Ba'zi endpoint o'zgarmadi (neutral), ba'zisi versiyalangan
@Controller({ path: "products" })
export class ProductsController {
  constructor(private service: ProductsService) {}

  @Get()
  @Version(VERSION_NEUTRAL)                           // har versiyada bir xil (o'zgarmagan)
  hammasi() { return this.service.hammasi(); }

  @Get(":id")
  @Version("1")                                       // v1 format
  bittaV1(@Param("id") id: string) {
    return this.service.bitta(id).then((p) => ({ id: p.id, narx: p.narx }));
  }

  @Get(":id")
  @Version("2")                                       // v2 format (qo'shimcha)
  bittaV2(@Param("id") id: string) {
    return this.service.bitta(id).then((p) => ({
      id: p.id, narx: { miqdor: p.narx, valyuta: "UZS" }, chegirma: p.chegirma,
    }));
  }
}

Misol 5 — Deprecation header (2.6)

typescript
// Eski versiyani deprecated belgilash (ogohlantirish)
@Injectable()
export class DeprecationInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const res = context.switchToHttp().getResponse();
    const version = context.switchToHttp().getRequest().version;

    if (version === "1") {                            // v1 deprecated
      res.setHeader("Deprecation", "true");
      res.setHeader("Sunset", "2026-12-31");          // qachon to'xtaydi (2.6)
      res.setHeader("Link", '</v2/users>; rel="successor-version"');   // v2 ga ishora
    }
    return next.handle();
  }
}
//  v1 ishlatuvchi mijoz ogohlantirish ko'radi (response header)

Misol 6 — Header versioning (muqobil — 2.3)

typescript
// main.ts — header versioning (URL toza)
app.enableVersioning({
  type: VersioningType.HEADER,
  header: "X-API-Version",                            // X-API-Version: 2
  defaultVersion: "1",
});
// Mijoz: GET /users + header "X-API-Version: 2"  V2
// URL bir xil (/users), versiya header'da

Misol 7 — Swagger har versiya (8.8 — 2.8)

typescript
// Har versiya alohida Swagger hujjat (8.8)
const configV1 = new DocumentBuilder().setTitle("API v1").setVersion("1.0").build();
const docV1 = SwaggerModule.createDocument(app, configV1, {
  include: [UsersV1Controller, /* v1 controllers */],
});
SwaggerModule.setup("api/v1/docs", app, docV1);

const configV2 = new DocumentBuilder().setTitle("API v2").setVersion("2.0").build();
const docV2 = SwaggerModule.createDocument(app, configV2, {
  include: [UsersV2Controller],
});
SwaggerModule.setup("api/v2/docs", app, docV2);
//  har versiya o'z hujjati (mijoz qaysi versiyani ishlatsa, o'shani ko'radi)

Misol 8 — Versiya monitoring (kim ishlatadi — 2.6)

typescript
// Eski versiya kim ishlatishini kuzatish (deprecation rejasi uchun)
@Injectable()
export class VersionMetricsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const version = req.version || "1";
    // Metrika (8.15 — qaysi versiya, qancha so'rov)
    this.metrics.increment(`api.version.${version}`, { endpoint: req.route?.path });
    return next.handle();
  }
}
//  "v1 hali 30% so'rov"  deprecation muddatini rejalashtirib bo'lmaydi (mijozlar bor)
//  "v1 0.1%"  to'xtatish mumkin (deyarli hech kim ishlatmaydi)

Misol 9 — Versiyalashsiz o'zgarish (non-breaking — 2.2)

typescript
// Non-breaking — versiya KERAK EMAS (qo'shish)
@Controller({ path: "users", version: "1" })
export class UsersController {
  @Get(":id")
  async bitta(@Param("id") id: string) {
    const u = await this.service.bitta(id);
    return {
      name: u.ism,
      phone: u.telefon,
      // YANGI ixtiyoriy maydon qo'shish — non-breaking (eski mijoz e'tiborsiz qoldiradi)
      avatar: u.avatar,                               // qo'shish OK (versiyasiz)
      //  "name" o'chirish/o'zgartirish — breaking (versiya kerak)
    };
  }
}
//  iloji boricha non-breaking (versiyasiz) — versiya oxirgi chora (2.7)

Misol 10 — Versiyalash arxitekturasi (to'liq)

text
  src/
  ├── users/
  │   ├── users.service.ts           (umumiy biznes — versiyasiz — Misol 2)
  │   ├── controllers/
  │   │   ├── users-v1.controller.ts (Misol 2)
  │   │   └── users-v2.controller.ts
  │   ├── dto/
  │   │   ├── user-v1.dto.ts          (eski format — Misol 3)
  │   │   └── user-v2.dto.ts          (yangi format)
  │   └── users.module.ts
  ├── common/
  │   ├── deprecation.interceptor.ts (Misol 5)
  │   └── version-metrics.interceptor.ts (Misol 8)

  Strategiya:
  - Service qatlami — versiyasiz (DRY — biznes mantiq bir marta)
  - Controller + DTO — versiyalangan (faqat format farqi)
  - Non-breaking  versiyasiz qo'shish 2.2-bob
  - Breaking  yangi versiya + eski deprecate 2.6-bob
  - 1-2 faol versiya (kam — 2.7)

Misol 11 — DTO versiyalash: class-transformer groups (2.5)

typescript
// Bitta DTO — @Expose({ groups }) bilan versiyaga qarab turli maydonlar
import { Expose, Type, plainToInstance } from "class-transformer";

export class UserResponseDto {
  @Expose({ groups: ["v1", "v2"] })                     // ikkala versiyada ham
  id: string;

  @Expose({ groups: ["v1"], name: "name" })             // faqat v1 (eski nom)
  ismV1: string;

  @Expose({ groups: ["v2"] })                           // faqat v2 (yangi nom)
  fullName: string;

  @Expose({ groups: ["v2"] })                           // v2'da qo'shilgan (yangi)
  @Type(() => ContactDto)
  contact: ContactDto;
}

// Controller — versiyaga qarab guruh tanlaydi (bitta DTO, ikki chiqish)
@Get()
async hammasi(@Req() req: Request) {
  const users = await this.service.hammasi();
  const guruh = req["version"] === "2" ? "v2" : "v1";   // versiyadan guruh
  return plainToInstance(UserResponseDto, users, { groups: [guruh] });
}
//  transformer sinflar takrorini kamaytiradi (bitta DTO, guruh bilan versiyalash)

Misol 12 — MEDIA_TYPE va CUSTOM versioning (2.4)

typescript
// main.ts — MEDIA_TYPE (Accept sarlavhasi orqali — "toza REST")
app.enableVersioning({
  type: VersioningType.MEDIA_TYPE,
  key: "v=",                                            // Accept: application/json;v=2
  defaultVersion: "1",
});

// yoki CUSTOM — versiyani so'rovdan o'zingiz ajratasiz (masalan, subdomen)
app.enableVersioning({
  type: VersioningType.CUSTOM,
  extractor: (req: Record<string, any>): string => {
    const header = req.headers?.["accept-version"];      // "2024-01-01" kabi sana-versiya
    return typeof header === "string" ? header : "1";    // topilmasa — v1
  },
  defaultVersion: "1",
});
//  CUSTOM sana asosidagi versiyalash (Stripe uslubi) yoki subdomen uchun ham mos

Misol 13 — Har versiyani alohida test qilish (2.8)

typescript
// e2e — har versiya alohida sinaladi (v1 va v2 shartnomasi buzilmaganini tasdiqlash)
describe("Users API versioning", () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = moduleRef.createNestApplication();
    app.enableVersioning({ type: VersioningType.URI, defaultVersion: "1" });
    await app.init();
  });

  it("v1 eski formatni qaytaradi (name, phone)", () => {
    return request(app.getHttpServer())
      .get("/v1/users")
      .expect(200)
      .expect((res) => {
        expect(res.body[0]).toHaveProperty("name");      // v1 shartnomasi
        expect(res.body[0]).not.toHaveProperty("fullName");
      });
  });

  it("v2 yangi formatni qaytaradi (fullName, contact)", () => {
    return request(app.getHttpServer())
      .get("/v2/users")
      .expect(200)
      .expect((res) => {
        expect(res.body[0]).toHaveProperty("fullName");  // v2 shartnomasi
        expect(res.body[0].contact).toHaveProperty("phone");
      });
  });

  it("versiyasiz so'rov defaultVersion (v1) ni beradi", () => {
    return request(app.getHttpServer()).get("/users").expect(200)
      .expect((res) => expect(res.body[0]).toHaveProperty("name"));
  });

  afterAll(async () => await app.close());
});
//  har versiya shartnomasi test bilan qulflanadi (kelajakdagi o'zgarish v1'ni buzsa — test qizil)

5. To'g'ri va noto'g'ri holatlar

1) Breaking change versiyasiz

text
 maydon o'zgartirish to'g'ridan (eski mijoz buziladi — 2.1)
 yangi versiya (v2)

2) Har o'zgarishga yangi versiya

text
 non-breaking ham versiya (versiya inflyatsiyasi — 2.2)
 faqat breaking; non-breaking — versiyasiz

3) V1/V2 kod takrori

text
 butun mantiq ikki marta (2.5)
 umumiy service + versiyalangan DTO

4) Eski versiyani birdan to'xtatish

text
 ogohlantirishsiz o'chirish (mijoz sinadi — 2.6)
 deprecation (vaqt, ogohlantirish)

5) Ko'p versiya (3-4)

text
 boshqarib bo'lmaydigan yuk (2.7)
 1-2 faol versiya

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Mobil ilova buzildi (API o'zgardi)

Sababi: breaking change versiyasiz 2.1-bob. Yechimi: versiyalash (eski saqlash).

Xato 2 — Versiyalar kod takrori

Sababi: umumiy service yo'q 2.5-bob. Yechimi: service versiyasiz, DTO versiyalangan.

Xato 3 — Versioning keyin qo'shish qiyin

Sababi: boshidan rejalashtirmagan 2.8-bob. Yechimi: boshidan versiyalash (v1 dan).

Xato 4 — Eski versiya to'xtatildi, mijoz sindi

Sababi: deprecation yo'q 2.6-bob. Yechimi: ogohlantirish + vaqt + monitoring.

Xato 5 — Qaysi versiya kim ishlatadi noma'lum

Sababi: monitoring yo'q 2.6-bob. Yechimi: version metrics (Misol 8).

Xato 6 — defaultVersion yo'q (versiyasiz so'rov xato)

Sababi: versiyasiz so'rov mos kelmaydi. Yechimi: defaultVersion 2.4-bob.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • REST 5.7-bob: API dizayni.
  • Controller 8.1-bob: versiyalangan.
  • DTO 8.5-bob: Response DTO (format).
  • Swagger 8.8-bob: har versiya hujjat.
  • Interceptor 8.6-bob: deprecation, metrics.
  • Monitoring 8.15-bob: versiya foydalanishi.
  • Frontend/mobil (11): mijoz versiya.
  • GraphQL 8.17-bob: muqobil (versiyasiz evolyutsiya).

8. Eng yaxshi amaliyotlar (best practices)

  • Faqat breaking uchun versiya (non-breaking — versiyasiz — 2.2).
  • URI versioning (aniq, eng keng — 2.3).
  • Umumiy service + versiyalangan DTO (DRY — 2.5).
  • Kam versiya (1-2 faol — 2.7).
  • Deprecation (asta-sekin, ogohlantirish, vaqt — 2.6).
  • Hujjatlash (Swagger har versiya — 2.8).
  • Monitoring (versiya foydalanishi — 2.6).
  • defaultVersion (versiyasiz so'rov — 2.4).
  • Boshidan rejalashtir (v1 dan — 2.8).
  • Non-breaking yo'lini izla (versiya oxirgi chora — 2.7).

9. Amaliy loyiha: "Versiyalangan API"

API versioning'ni amalda mustahkamlash.

Maqsad

API'ni v1/v2 bilan versiyalash: umumiy service, versiyalangan DTO, deprecation, monitoring.

Talablar (requirements)

  1. Setup: enableVersioning URI (Misol 1, 2.4).
  2. V1/V2 controller: umumiy service (Misol 2, 2.5).
  3. Versiyalangan DTO: format farqi (Misol 3, 2.5).
  4. Metod versiya: aralash + neutral (Misol 4, 2.4).
  5. Deprecation: header + sunset (Misol 5, 2.6).
  6. Swagger: har versiya hujjat (Misol 7, 2.8).
  7. Monitoring: versiya metrics (Misol 8, 2.6).
  8. Non-breaking: versiyasiz qo'shish (Misol 9, 2.2).
  9. Strategiya: kam versiya 2.7-bob.
  10. Breaking aniqlash: qachon versiya 2.2-bob.

Maslahatlar (hint)

  • Faqat breaking (2.2, 2-xato).
  • Umumiy service (2.5, 2-xato).
  • Deprecation vaqt (2.6, 4-xato).
  • Monitoring (Misol 8, 5-xato).
  • defaultVersion (2.4, 6-xato).

"Tayyor" mezonlari (acceptance criteria)

  • Setup (URI).
  • V1/V2 (umumiy service).
  • Versiyalangan DTO.
  • Metod versiya.
  • Deprecation.
  • Swagger.
  • Monitoring.
  • Non-breaking.
  • Kam versiya.
  • Breaking aniqlash.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda API versioning'ni o'rgandik:

  • Nega versioning (breaking change — 2.1); breaking vs non-breaking 2.2-bob; usullar (URI/header/media — 2.3).
  • NestJS versioning (built-in — 2.4); kod ulashish (umumiy service + DTO — DRY — 2.5).
  • Deprecation (asta-sekin, ogohlantirish — 2.6); strategiya (kam versiya — 2.7).

Keyingi bob — 8.28: Geolokatsiya va xaritalar. Versioning'ni bildik; endi yana bir real mavzu — geolokatsiya (joylashuv, masofa, eng yaqin, geo-qidiruv — yetkazib berish, taksi, do'kon) — ni o'rganamiz. Yetkazib berish/lokatsiya ilovalarida zarur.


Foydalanilgan rasmiy/ishonchli manbalar

  • NestJS rasmiy hujjati — Versioning: VersioningType (URI/HEADER/MEDIA_TYPE/CUSTOM), enableVersioning, @Version, VERSION_NEUTRAL, defaultVersion.
  • NestJS rasmiy hujjati — OpenAPI (Swagger): har versiya uchun alohida hujjat (include, SwaggerModule.setup).
  • class-transformer hujjati — @Expose({ groups }), plainToInstance (DTO versiyalash guruhlar orqali).
  • HTTP standartlari — Deprecation (RFC 8594 loyihasi) va Sunset (RFC 8594) javob sarlavhalari; Link sarlavhasi (RFC 8288, rel="successor-version").
  • Semantic Versioning (SemVer 2.0.0) — major/minor/patch tamoyili (breaking = major).
  • Sanoat amaliyoti — Stripe (sana asosidagi versiyalash), GitHub (media type / header versioning), Google Cloud API dizayn qo'llanmasi (deprecation siyosati va migratsiya).

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.27-bob: API versioning — Wisar