IlmHamroh
Dasturlash kitobi (eski nashr)/8-QISM — NestJS27/30-dars21 daqiqa
Mundarija (38)

NestJS'da API versioning — v1, v2 va deprecation (8.27-bob)

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 bering (mobil — oylar kerak); birdan to'xtatmang

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 saqlang (har versiya — qo'shimcha yuk):
  - 1-2 faol versiya (joriy + oldingi)
  - Eskisini deprecate qiling (yangi qo'shilganda)
  - "Versiyasiz qancha mumkin" (non-breaking — 2.2)

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

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

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 (v1→v2 — 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 (v1→v2 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 (v1→v2 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 — v1↔v2 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 (v1→v2 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-bob
✅ 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-bob
✅ 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).
Ulashish:Telegram'da

O'qiganingizni mustahkamlang

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS'da API versioning — v1, v2 va deprecation (8.27-bob) — IlmHamroh