IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API25/51-dars16 daqiqa
Mundarija (30)

API versiyalash va deprecation: /v1, buzuvchi o'zgarish, Deprecation va Sunset sarlavhalari

Qisqacha: API'ni o'zgartirganda eski mijozlar ishlashda davom etishi kerak. Yangi ixtiyoriy maydon yoki yangi endpoint — buzmaydigan o'zgarish, versiya kerak emas. Maydonni o'chirish, nomini yoki turini o'zgartirish, xato formatini almashtirish — buzuvchi o'zgarish: yangi versiya (/v2) chiqariladi, eskisi bir muddat yashaydi. Eski yo'l javobiga Deprecation (RFC 9745) — "qachondan eskirgan", Sunset (RFC 8594) — "qachon o'chadi" va Link: <…>; rel="successor-version" qo'yiladi. O'zgarishlar tarixi — changelog'da.

Bu darsda

  • Qaysi o'zgarish mijozni buzadi, qaysi biri buzmasligini ajrata olasiz.
  • Versiyani URI, sarlavha yoki media turida berishning afzallik va kamchiliklarini taqqoslaysiz.
  • Deprecation, Sunset va successor-version sarlavhalarini to'g'ri formatda qo'yasiz va mijozda o'qiysiz.
  • Changelog va "eskirish muddati" bilan API hayot siklini rejalashtirasiz.
  • vazifalar-api ni /v1 ga ko'chirasiz va eski yo'llarni eskirgan deb belgilaysiz.

Oldin bilishingiz kerak: REST prinsiplari va resurslar, Xato javoblari: Problem Details, express.Router va qatlamli loyiha tuzilmasi, Semver va versiya diapazonlari.

1. Nega bu kerak?

Problem Details darsida xato formatini o'zgartirdik: { "xato": "…" } o'rniga { type, title, detail … }. Server uchun bu yaxshilanish. Lekin 22-qismdagi frontend xato maydonini o'qiydi — endi u xabarni topmaydi va foydalanuvchiga "Server xatosi: 422" ko'rsatadi. Biz frontend'ni buzdik.

Frontend o'zimizniki bo'lsa — uni ham tuzatamiz. Lekin API'ga telefon ilovasi ham ulangan deylik. Uning eski versiyasi Play Market'da, minglab telefonlarda o'rnatilgan va oylar davomida yangilanmaydi. Server bir lahzada o'zgaradi, mijozlar esa yo'q.

O'xshatish: avtobus yo'nalishi. Shahar 45-avtobus yo'nalishini o'zgartirmoqchi. Ertalab to'satdan o'zgartirsa — odamlar bekatda kutib qoladi. To'g'ri yo'li: yangi yo'nalishga yangi raqam (45A) beriladi, eski bekatlarda "bu yo'nalish 1-yanvardan yopiladi, 45A ga o'ting" e'loni osiladi. Versiya — yangi raqam; Deprecation va Sunset — bekatdagi e'lon.

2. Buzuvchi va buzmaydigan o'zgarish

2.1 Ikki toifa

Buzuvchi o'zgarish (breaking change) — eski, o'zgartirilmagan mijozni ishdan chiqaradigan o'zgarish. Buzmaydigan (non-breaking) — eski mijoz sezmaydi.

O'zgarish Toifa Nega
javobga yangi maydon (versiya) buzmaydi* eski mijoz uni o'qimaydi
yangi endpoint (/eksportlar) buzmaydi eski mijoz uni chaqirmaydi
so'rovga yangi IXTIYORIY parametr buzmaydi yubormasa — sukut ishlaydi
maydonni o'chirish yoki nomini o'zgartirish buzadi mijoz undefined oladi
maydon turini o'zgartirish (id: son → satr) buzadi id + 1 endi "71"
so'rovga yangi MAJBURIY maydon buzadi eski so'rov endi 422
xato formatini almashtirish buzadi 22-qism frontend'i kabi
status kodni o'zgartirish (200 → 201) buzadi status === 200 tekshiruvi
enum'ga yangi qiymat ehtiyot mijoz switch ida default bo'lmasa

2.2 Yulduzcha: "tolerant o'quvchi"

Yangi maydon faqat mijoz noma'lum maydonlarga chidamli bo'lsa buzmaydi. Bu qoida tolerant o'quvchi (tolerant reader) deb ataladi: "o'zing kutganini o'qi, qolganini e'tiborsiz qoldir". Quyidagi brauzer blokida ikkita mijoz bir xil yangi javobni o'qiydi:

js
const oldShape = ["id", "matn", "bajarildi"];

function strictRead(task) {
  const keys = Object.keys(task);
  const extra = keys.filter((k) => !oldShape.includes(k));
  if (extra.length) throw new Error(`noma'lum maydon: ${extra}`);
  return task;
}

function tolerantRead(task) {
  return { id: task.id, matn: task.matn, bajarildi: task.bajarildi };
}

// server javobiga yangi "versiya" maydoni qo'shildi
const fromServer = {
  id: 7, matn: "Non olish", bajarildi: false, versiya: 2,
};
console.log(tolerantRead(fromServer));
try {
  strictRead(fromServer);
} catch (error) {
  console.log(error.message);
}

Konsolda:

text
{ id: 7, matn: 'Non olish', bajarildi: false }
noma'lum maydon: versiya

Qattiq mijoz buzildi — server faqat maydon QO'SHGAN bo'lsa ham. Bu real tuzoq: Zod'dagi z.strictObject ni javobni tekshirishga ishlatsangiz, xuddi shunday bo'ladi. So'rovni qattiq tekshirish — to'g'ri (server himoyasi). Javobni o'qishda esa oddiy z.object (ortiqchani tashlab yuboradi) — to'g'ri.

Tekshirib ko'ring: Server bajarildi: boolean o'rniga holat: "yangi" | "bajarildi" | "bekor" qo'ymoqchi va bajarildi ni olib tashlamoqchi. Buzuvchimi? Buzmaydigan qilib qanday chiqarish mumkin?

Javob

Buzuvchi — bajarildi o'chdi. Buzmaydigan yo'li: holat ni QO'SHING, bajarildi ni ham qoldiring (holat === "bajarildi" dan hisoblanadi). Eski mijozlar bajarildi ni o'qishda davom etadi, yangilari holat ni. bajarildi ni o'chirish — keyingi katta versiyada (/v2), eskirish e'loni bilan.

3. Versiya qayerda turadi

3.1 Uch usul

Usul Misol Afzallik Kamchilik
URI GET /v1/vazifalar ko'rinadi, brauzer va curl'da oson, kesh URI bo'yicha ajratadi "resurs bitta, URI ikkita" — REST nazariyasiga to'liq mos emas
Sarlavha X-API-Version: 2 yoki Api-Version: 2026-10-24 URI toza brauzerda sinash qiyin, CORS ro'yxatiga kerak, kesh Vary siz adashadi
Media turi Accept: application/vnd.vazifa.v2+json HTTP g'oyasiga eng yaqin eng murakkab; ko'p vosita qo'llamaydi

Katta kompaniyalar turlicha tanlagan:

  • Google (API Improvement Proposals, 185-band) — asosiy versiya URI'da: /v1/….
  • GitHub REST — sana bilan sarlavhada: X-GitHub-Api-Version: 2022-11-28.
  • Stripe — sana bilan sarlavhada (Stripe-Version), har akkauntning sukut versiyasi bor.

Sana bilan versiyalash — kichik, tez-tez o'zgarishlar uchun. URI'dagi v1 — kamdan-kam, katta o'zgarishlar uchun.

3.2 Kanon tanlovi — URI

vazifalar-api versiyani URI'da beradi: /v1/vazifalar. Sabablar (kanon QADAMLAR):

  1. O'quvchi brauzer va curl bilan sinaydi — sarlavha qo'shish shart emas.
  2. CORS'da qo'shimcha sarlavha kerak emas.
  3. Keshlar (brauzer, CDN) URI bo'yicha ishlaydi — Vary haqida o'ylash shart emas (HTTP kesh darsi).

Faqat asosiy (major) raqam URI'da. Semver darsida ko'rgan 1.4.2 — paketlar uchun. API'da v1.4 yo'q: buzmaydigan o'zgarishlar v1 ichida jim qo'shiladi, faqat buzuvchisi v2 ni talab qiladi.

Maslahat: v2 chiqarish — qimmat qaror: ikki versiyani bir vaqtda qo'llab-quvvatlash, ikki xil test, ikki xil hujjat. Avval buzmaydigan yo'lni qidiring (yangi maydon qo'shish, eskisini qoldirish). v2 — oxirgi chora.

4. Eskirish: Deprecation, Sunset va voris havolasi

4.1 Uchta sarlavha

Eski yo'l darhol o'chirilmaydi. U ishlashda davom etadi, lekin har javobda ogohlantirish sarlavhalari bo'ladi:

http
HTTP/1.1 200 OK
Deprecation: @1792800000
Sunset: Sun, 24 Jan 2027 00:00:00 GMT
Link: </v1/vazifalar/1>; rel="successor-version"
Sarlavha Standart Ma'nosi Format
Deprecation RFC 9745 (2025-03) shu sanadan boshlab eskirgan @ + Unix vaqti (soniya)
Sunset RFC 8594 (2019-05) shu sanadan keyin ishlamasligi mumkin HTTP sana (Sun, 24 Jan 2027 00:00:00 GMT)
Link … rel="successor-version" RFC 5829 o'rniga nima ishlatish URI burchak qavslarda

Eskirgan (deprecated) — "ishlaydi, lekin ishlatmang, o'rnini bosuvchisi bor". Sunset (quyosh botishi) — "shu kundan keyin o'chiriladi". Ikkalasi alohida: biri ogohlantirish, ikkinchisi muddat.

Ikki sarlavhaning formati har xil ekaniga e'tibor bering. Deprecation yangiroq standart va u "structured field" sanasi: @ va soniyalar. Eski qoralamalarda Deprecation: true ko'rinishi uchraydi — RFC 9745 da u yo'q.

4.2 Qiymatlarni hisoblash

Bu blok brauzerda ham ishlaydi:

js
const deprecatedAt = Date.UTC(2026, 9, 24); // oy 0 dan: 9 = oktabr
const sunsetAt = Date.UTC(2027, 0, 24);

console.log(`Deprecation: @${deprecatedAt / 1000}`);
console.log(`Sunset: ${new Date(sunsetAt).toUTCString()}`);

function daysLeft(sunsetHeader, now) {
  const ms = Date.parse(sunsetHeader) - now;
  return Math.ceil(ms / 86_400_000);
}
const now = Date.UTC(2026, 11, 1);
console.log(daysLeft(new Date(sunsetAt).toUTCString(), now));
console.log(new Date(Number("1792800000") * 1000).toISOString());

Konsolda:

text
Deprecation: @1792800000
Sunset: Sun, 24 Jan 2027 00:00:00 GMT
54
2026-10-24T00:00:00.000Z

Date.UTC oyni 0 dan sanaydi — 9 oktabr, 0 yanvar (Date asoslari). Date.now() millisekund beradi, Deprecation esa soniya kutadi — 1000 ga bo'lish shart. toUTCString() aynan HTTP sana formatini beradi. 2026-12-01 dan Sunset'gacha — 54 kun.

4.3 Mijoz ogohlantirishni o'qiydi

Sarlavha qo'yish yarim ish. Mijoz uni o'qib, dasturchiga ko'rsatishi kerak — odatda logga yoki dev konsolga. Quyidagi blok Node uchun:

js
import { createServer } from "node:http";

const OLD = ["/vazifalar", "/teglar"];
const server = createServer((req, res) => {
  const path = req.url;
  const old = OLD.find((p) => path === p || path.startsWith(p + "/"));
  if (old) {
    res.setHeader("Deprecation", "@1792800000");
    res.setHeader("Sunset", "Sun, 24 Jan 2027 00:00:00 GMT");
    res.setHeader("Link", `</v1${path}>; rel="successor-version"`);
  }
  const one = { id: 1, matn: "Non olish" };
  const body = path.endsWith("/1") ? one : [];
  res.setHeader("content-type", "application/json");
  res.end(JSON.stringify(body));
});
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
const base = `http://127.0.0.1:${server.address().port}`;

async function get(path) {
  const res = await fetch(base + path);
  if (res.headers.has("deprecation")) {
    const sunset = res.headers.get("sunset");
    const link = res.headers.get("link");
    console.log(`OGOH: ${path} eskirgan`);
    console.log(`  o'chadi: ${sunset}`);
    console.log(`  voris: ${link}`);
  }
  return res.json();
}

console.log(await get("/v1/vazifalar/1"));
console.log(await get("/vazifalar/1"));
server.closeAllConnections();
server.close();

Konsolda:

text
{ id: 1, matn: 'Non olish' }
OGOH: /vazifalar/1 eskirgan
  o'chadi: Sun, 24 Jan 2027 00:00:00 GMT
  voris: </v1/vazifalar/1>; rel="successor-version"
{ id: 1, matn: 'Non olish' }

Ikkala yo'l bir xil ma'lumot qaytaradi — eski yo'l ishlayapti. Farq faqat sarlavhalarda. Mijoz ogohlantirishni bir marta ko'rsatishi yetarli (har so'rovda logni to'ldirmaslik uchun — buni 3-mashqda qilasiz).

Tekshirib ko'ring: Nega Deprecation da millisekund (1792800000000) yozish xato?

Javob

RFC 9745 Unix vaqtini soniyada kutadi. Millisekund bilan mijoz uni taxminan 58 800-yildagi sana deb o'qiydi — ogohlantirish ma'nosiz bo'ladi. JS'da Date.now() va Date.UTC() millisekund qaytaradi, shuning uchun / 1000 (va kerak bo'lsa Math.floor).

5. Hayot sikli va changelog

5.1 Yo'lning hayoti

Eski yo'l qanday bosqichlardan o'tadi — diagrammadan kuzating:

stateDiagram-v2
  [*] --> Faol
  Faol --> Eskirgan: v1 chiqdi (Deprecation)
  Eskirgan --> Ochirilgan: Sunset sanasi o'tdi
  Ochirilgan --> [*]
  note right of Eskirgan
    ishlaydi, lekin har javobda
    Deprecation + Sunset + Link
  end note

Muddat qancha bo'lsin? Qoida yo'q, lekin mijozlar kimligiga bog'liq:

Mijozlar Odatiy muddat
faqat o'z frontend'ingiz 1–4 hafta
o'z mobil ilovangiz 3–6 oy (eski versiyalar telefonlarda qoladi)
tashqi dasturchilar (ochiq API) 6–24 oy

Kanon 3 oy tanlagan: Sunset — 2027-01-24. Sunset'dan keyin eski yo'l odatda 410 Gone qaytaradi ("bu resurs bor edi va ataylab olib tashlandi") — 404 dan aniqroq. Kanonda bu bosqich hali yo'q: u 2027-yanvarda keladi.

5.2 Changelog

Changelog (o'zgarishlar tarixi) — har versiyada nima o'zgargani yozilgan hujjat. Sarlavhalar mashina uchun, changelog — odam uchun. Unda uch narsa bo'lsin: sana, o'zgarish, buzuvchimi yoki yo'q. Kanondagi API.md (keyingi darslarda yoziladi) shunday tugaydi:

text
## O'zgarishlar tarixi (changelog)

- v1 (2026-10-24): /v1 prefiksi; xatolar Problem Details;
  ro'yxat — { versiya, vazifalar, sahifa }; versiya maydoni
  (ETag); eksport, toplam. Eski versiyasiz yo'llar
  (/vazifalar, /teglar) — eskirgan (Deprecation),
  2027-01-24 da o'chiriladi (Sunset).

Real loyihalarda yana uchta kanal ishlatiladi: email ro'yxati ("API yangiliklari"), hujjat sahifasidagi banner va eskirgan yo'lni chaqirayotgan mijozlarni logdan topib, ularga alohida yozish.

6. API qadami: /v1 va eskirgan yo'llar (holat 26, 1-qism)

Bu qadam holat 24 dan boshlanadi. Commit — keyingi darsda, HTTP kesh bilan birga (holat 26). Hozircha commit qilmang.

6.1 Nega aynan hozir

Holat 24 da xato formati o'zgardi — bu buzuvchi o'zgarish. Kanon qarori: v1 aynan shu formatdan boshlanadi. Eski versiyasiz yo'llar ham ishlaydi (frontend birdan sinmasin), lekin eskirgan deb belgilanadi.

6.2 src/ilova.ts — v1 router

ts
// Versiya URI'da: /v1/... Buzuvchi o'zgarish bo'lsa — /v2,
// v1 bir muddat yashaydi
const v1 = express.Router();
v1.use("/vazifalar", idempotentlik(idempotentlikOmbori),
  vazifalarRouteri(xizmat));
v1.use("/teglar", teglarRouteri(xizmat));
app.use("/v1", v1);
// Eski versiyasiz yo'llar — ishlaydi, lekin eskirgan:
// Deprecation + Sunset + vorisga havola
app.use(eskirgan(ESKI_YOLLAR), v1);

Bitta v1 router ikki joyga ulandi. /v1/vazifalar — oddiy. /vazifalar esa avval eskirgan middleware'idan o'tadi, keyin xuddi o'sha router'ga tushadi (Router darsi). Mantiq takrorlanmaydi — ikkala yo'l bir xil kodni ishlatadi.

Ikki so'rovni yonma-yon kuzating:

Qadam Middleware GET /v1/vazifalar/1 GET /vazifalar/1
1 app.use("/v1", v1) mos — v1 ga kiradi mos emas — o'tkazib yuboriladi
2 eskirgan(ESKI_YOLLAR) yetib kelmaydi /vazifalar prefiksi topildi — 3 sarlavha qo'yildi
3 v1 router → vazifalarRouteri 200 + vazifa 200 + xuddi o'sha vazifa

Farq faqat ikkinchi qadamda: eski yo'l javobiga ogohlantirish sarlavhalari qo'shiladi.

6.3 src/umumiy/eskirgan.ts

Yangi fayl — to'liq (kanondan; qatorlar telefon uchun bo'lingan):

ts
// eskirgan.ts — eskirgan (deprecated) yo'llar: ishlaydi, lekin har
// javobda ogohlantirish sarlavhalari. Deprecation (RFC 9745):
// qachondan eskirgan (@unix-vaqt); Sunset (RFC 8594): qachon
// o'chiriladi; Link rel="successor-version"
import type { RequestHandler } from "express";

export type EskiYol = { prefiks: string; voris: string };

export const ESKI_YOLLAR: readonly EskiYol[] = [
  { prefiks: "/vazifalar", voris: "/v1/vazifalar" },
  { prefiks: "/teglar", voris: "/v1/teglar" },
];
/** 2026-10-24 00:00 UTC — /v1 chiqqan kun */
export const ESKIRGAN_VAQT = Date.UTC(2026, 9, 24);
/** 3 oydan keyin o'chiriladi */
export const QUYOSH_BOTISHI = Date.UTC(2027, 0, 24);

export function eskirgan(yollar: readonly EskiYol[]): RequestHandler {
  return (req, res, next) => {
    const y = yollar.find((e) =>
      req.path === e.prefiks || req.path.startsWith(e.prefiks + "/"));
    if (y !== undefined) {
      res.set("Deprecation", `@${String(ESKIRGAN_VAQT / 1000)}`);
      res.set("Sunset", new Date(QUYOSH_BOTISHI).toUTCString());
      const qolgan = req.path.slice(y.prefiks.length);
      res.append("Link",
        `<${y.voris}${qolgan}>; rel="successor-version"`);
    }
    next();
  };
}

Uch nozik joy:

  • startsWith(e.prefiks + "/") — /vazifalarim kabi boshqa yo'l tasodifan mos kelmasin.
  • res.append("Link", …) — set emas: sahifalash darsidagi rel="next" havolasi ham Link sarlavhasida. append ikkalasini saqlaydi.
  • Sanalar konstanta — har so'rovda yangi hisoblanmaydi va test ularni aniq solishtira oladi.

6.4 CORS

Brauzer fetch i javob sarlavhalarining faqat "oddiy" qismini o'qiy oladi (CORS mijoz tomondan). Deprecation, Sunset, Link ni frontend ko'rishi uchun ular exposedHeaders ro'yxatiga qo'shildi (keyingi darsdagi ETag bilan birga):

ts
exposedHeaders: ["Location", "X-Request-Id", "Idempotency-Replayed",
  "ETag", "Link", "Deprecation", "Sunset"],

6.5 Natija (kanon curl, holat 26)

bash
curl -i $A/vazifalar/1
text
HTTP/1.1 200 OK
Deprecation: @1792800000
Sunset: Sun, 24 Jan 2027 00:00:00 GMT
Link: </v1/vazifalar/1>; rel="successor-version"
Content-Type: application/json; charset=utf-8

{"id":1,"matn":"Non olish #uy","bajarildi":true,"teglar":["uy"],…}

Hamma testlar endi /v1/... ga o'tkaziladi, eski yo'l uchun alohida test — sarlavhalar borligini tekshiradi. Holat 26 dagi ETag va versiya maydoni — keyingi darsda.

7. Ko'p uchraydigan xatolar

1. "Kichik" o'zgarishni versiyasiz chiqarish. "Faqat maydon nomini bajarildi dan tugadi ga o'zgartirdim" — eng ko'p uchraydigan buzilish. Nom o'zgarishi = o'chirish + qo'shish.

2. Har o'zgarishga yangi versiya. v1, v2, … v9 — ularni qo'llab-quvvatlab bo'lmaydi. Buzmaydigan o'zgarishlar v1 ichida.

3. Eskirgan yo'lni ogohlantirishsiz o'chirish. Sunset'siz o'chirish — mijozlar uchun to'satdan 404.

4. Deprecation da millisekund yoki true. RFC 9745: @ + soniya.

5. Mijozda javobni qattiq tekshirish. z.strictObject bilan javob o'qisa, server maydon qo'shganda mijoz buziladi. Javob — tolerant o'qiladi.

6. Link ni set bilan qo'yish. Boshqa havola (rel="next") o'chib ketadi.

8. Mashqlar

Yechimlar kurs/mashqlar/25/25-versiyalash/ da.

1-mashq (oson): buzadimi?

Har o'zgarish uchun "buzadi" yoki "buzmaydi" yozing:

  • Javobga yaratuvchi maydoni qo'shildi —
  • GET /teglar endi [{ teg, soni }] o'rniga { teglar: [...] } qaytaradi —
  • POST /vazifalar ga ixtiyoriy muddat maydoni qo'shildi —
  • limit sukut qiymati 20 dan 50 ga o'zgardi —
  • DELETE muvaffaqiyatda 204 o'rniga 200 qaytaradi —
Yechim
  • buzmaydi — tolerant o'quvchi uni e'tiborsiz qoldiradi.
  • buzadi — javob shakli o'zgardi: massiv o'rniga obyekt.
  • buzmaydi — eski so'rov uni yubormaydi, sukut ishlaydi.
  • buzadi — ko'zga ko'rinmas, lekin mijoz 20 talik sahifaga moslangan bo'lishi mumkin (UI, sahifa soni). Sukut qiymatlar ham shartnoma.
  • buzadi — status === 204 tekshiruvi yoki "tana yo'q" taxmini sinadi.

2-mashq (o'rta): sarlavhalarni yasash

deprecationHeaders(deprecatedIso, sunsetIso, successor) yozing. U uchta sarlavhali obyekt qaytarsin. Sunset Deprecation'dan oldin bo'lsa — Error tashlasin. Bu toza JS — «Ishga tushir» bilan ishlaydi.

Yechim
js
function deprecationHeaders(deprecatedIso, sunsetIso, successor) {
  const dep = Date.parse(deprecatedIso);
  const sun = Date.parse(sunsetIso);
  if (Number.isNaN(dep) || Number.isNaN(sun)) {
    throw new Error("sana noto'g'ri");
  }
  if (sun <= dep) {
    throw new Error("Sunset Deprecation'dan keyin bo'lsin");
  }
  return {
    Deprecation: `@${Math.floor(dep / 1000)}`,
    Sunset: new Date(sun).toUTCString(),
    Link: `<${successor}>; rel="successor-version"`,
  };
}

console.log(deprecationHeaders(
  "2026-10-24T00:00:00Z", "2027-01-24T00:00:00Z", "/v1/teglar",
));
try {
  deprecationHeaders("2027-01-24", "2026-10-24", "/x");
} catch (error) {
  console.log(error.message);
}

Konsolda:

text
{
  Deprecation: '@1792800000',
  Sunset: 'Sun, 24 Jan 2027 00:00:00 GMT',
  Link: '</v1/teglar>; rel="successor-version"'
}
Sunset Deprecation'dan keyin bo'lsin

3-mashq (qiyin): bir marta ogohlantiruvchi mijoz

createApiClient(fetchFn, warn) yozing. U get(path) qaytarsin. Javobda Deprecation bo'lsa — warn ni chaqirsin, lekin har yo'l uchun bir marta. Link dan voris manzilini ajratib (< va > orasi) xabarga qo'shsin. Soxta fetch bilan sinang: bitta eskirgan yo'lni 3 marta, yangisini 1 marta so'rang.

Yechim
js
function createApiClient(fetchFn, warn) {
  const warned = new Set();
  return {
    async get(path) {
      const res = await fetchFn(path);
      if (res.headers.has("deprecation") && !warned.has(path)) {
        warned.add(path);
        const link = res.headers.get("link") ?? "";
        const re = /<([^>]+)>;\s*rel="successor-version"/;
        const next = re.exec(link);
        const sunset = res.headers.get("sunset") ?? "noma'lum";
        warn(`${path} eskirgan (${sunset}) → ${next?.[1] ?? "?"}`);
      }
      return res.json();
    },
  };
}

const fakeFetch = async (path) => {
  const headers = path.startsWith("/v1") ? {} : {
    deprecation: "@1792800000",
    sunset: "Sun, 24 Jan 2027 00:00:00 GMT",
    link: `</v1${path}>; rel="successor-version"`,
  };
  return new Response("[]", { headers });
};

const warn = (m) => console.log("OGOH:", m);
const client = createApiClient(fakeFetch, warn);
for (let i = 0; i < 3; i++) await client.get("/teglar");
await client.get("/v1/teglar");
console.log("tugadi");

Konsolda:

text
OGOH: /teglar eskirgan (Sun, 24 Jan 2027 00:00:00 GMT) → /v1/teglar
tugadi

Uch so'rov — bitta ogohlantirish. Regex Link ichidan faqat successor-version havolasini oladi: sarlavhada rel="next" ham bo'lishi mumkin.

4-mashq: API qadami

O'z vazifalar-api ingizda «API qadami» bo'limidagi o'zgarishlarni qiling: v1 router, eskirgan.ts, CORS exposedHeaders. Hamma testlarni /v1/... ga o'tkazing va bitta yangi test yozing: GET /vazifalar/1 javobida Deprecation: @1792800000, Sunset va Link: </v1/vazifalar/1>; rel="successor-version" bor; GET /v1/vazifalar/1 da Deprecation yo'q. npm run check toza bo'lsin. Commit qilmang — keyingi darsda.

9. Real ishda

  • Telegram Bot API, GitHub, Stripe — hammasi changelog yuritadi. GitHub hujjatiga ko'ra (2026-10 holatiga), eski REST versiyasi yangisi chiqqandan keyin kamida 24 oy ishlaydi. Yopilishi yaqinlashganda javoblarda Deprecation va Sunset sarlavhalari paydo bo'ladi, yopilgandan keyin esa — 410 Gone.
  • Mobil ilovali kompaniyalarda (Uzum, Yandex Go) "minimal qo'llab-quvvatlanadigan versiya" ham bor: juda eski ilova serverdan "yangilang" javobini oladi.
  • NestJS (30-qism) versiyalashni o'zi qo'llaydi: app.enableVersioning({ type: VersioningType.URI }) — URI, sarlavha yoki media turi.
  • OpenAPI hujjatida (deprecated: true) eskirgan operatsiya chizilgan holda ko'rinadi (OpenAPI darsi).
  • Intervyu savoli: "API'ni mijozlarni buzmasdan qanday o'zgartirasiz?" — buzmaydigan o'zgarishlar, tolerant o'quvchi, faqat major versiya URI'da, Deprecation/Sunset, changelog.

Xulosa

  • Yangi maydon, endpoint yoki ixtiyoriy parametr — buzmaydi; o'chirish, nomni/turni/statusni/formatni o'zgartirish — buzadi.
  • Javobni tolerant o'qing; so'rovni esa qattiq tekshiring.
  • Versiya URI'da (/v1) — eng sodda; faqat major raqam.
  • Eski yo'l darhol o'chmaydi: Deprecation: @<soniya>, Sunset: <HTTP sana>, Link: <…>; rel="successor-version".
  • Changelog — odam uchun; sarlavhalar — mashina uchun; Sunset'dan keyin — 410 Gone.

Keyingi dars: HTTP caching va shartli so'rovlar — ETag, If-None-Match va 304 bilan trafikni tejaymiz, If-Match va 412 bilan esa ikki kishining bir vaqtdagi tahririda ma'lumot yo'qolishini to'xtatamiz.

Manbalar

  • RFC 9745 "The Deprecation HTTP Response Header Field" (2025-03) — rfc-editor.org/rfc/rfc9745
  • RFC 8594 "The Sunset HTTP Header Field" (2019-05) — rfc-editor.org/rfc/rfc8594
  • RFC 5829 "Link Relation Types for Simple Version Navigation" (2010-04) — rfc-editor.org/rfc/rfc5829
  • Google AIP 185 "API Versioning" — google.aip.dev/185
  • GitHub REST API versions — docs.github.com/rest/about-the-rest-api/api-versions
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
API versiyalash va deprecation: /v1, buzuvchi o'zgarish, Deprecation va Sunset sarlavhalari — IlmHamroh