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)
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
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)
┌──────────────┬──────────────────────────────────────────────┐
│ 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)
// 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/usersV1 controller,/v2/usersV2.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:
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). Extractorstring(bitta versiya) yokistring[](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)
// 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)
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'xtatmaDeprecation (eski versiyani to'xtatish — to'g'ri usul): asta-sekin (birdan emas). E'lon (oldindan — "v1 6 oydan keyin to'xtaydi"), ogohlantirish header (
Deprecation,Sunsetsana), 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)
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)
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 xaritaMigratsiyada 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).
// 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)
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 kerakGraphQL'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):
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(yokideprecationReason) 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)
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
// 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 versiyada4. Batafsil kod namunalari
Misol 1 — Versioning setup (2.4)
// 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)
// 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)
// 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)
// 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)
// 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)
// 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'daMisol 7 — Swagger har versiya (8.8 — 2.8)
// 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)
// 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)
// 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)
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)
// 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)
// 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 mosMisol 13 — Har versiyani alohida test qilish (2.8)
// 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
maydon o'zgartirish to'g'ridan (eski mijoz buziladi — 2.1)
yangi versiya (v2)2) Har o'zgarishga yangi versiya
non-breaking ham versiya (versiya inflyatsiyasi — 2.2)
faqat breaking; non-breaking — versiyasiz3) V1/V2 kod takrori
butun mantiq ikki marta (2.5)
umumiy service + versiyalangan DTO4) Eski versiyani birdan to'xtatish
ogohlantirishsiz o'chirish (mijoz sinadi — 2.6)
deprecation (vaqt, ogohlantirish)5) Ko'p versiya (3-4)
boshqarib bo'lmaydigan yuk (2.7)
1-2 faol versiya6. 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)
- Setup: enableVersioning URI (Misol 1, 2.4).
- V1/V2 controller: umumiy service (Misol 2, 2.5).
- Versiyalangan DTO: format farqi (Misol 3, 2.5).
- Metod versiya: aralash + neutral (Misol 4, 2.4).
- Deprecation: header + sunset (Misol 5, 2.6).
- Swagger: har versiya hujjat (Misol 7, 2.8).
- Monitoring: versiya metrics (Misol 8, 2.6).
- Non-breaking: versiyasiz qo'shish (Misol 9, 2.2).
- Strategiya: kam versiya 2.7-bob.
- 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-transformerhujjati —@Expose({ groups }),plainToInstance(DTO versiyalash guruhlar orqali).- HTTP standartlari —
Deprecation(RFC 8594 loyihasi) vaSunset(RFC 8594) javob sarlavhalari;Linksarlavhasi (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!