IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime58/69-dars16 daqiqa
Mundarija (24)

Code-first OpenAPI: Zod'dan hujjat, TypeScript klient va breaking change tekshiruvi

Qisqacha: Code-first yondashuvda OpenAPI hujjati qo'lda yozilmaydi — u validatsiya uchun allaqachon bor Zod sxemalaridan avtomatik yasaladi (@asteasolutions/zod-to-openapi). Hujjat /openapi.json da beriladi va Swagger UI, Scalar yoki Redoc uni chiroyli sahifaga aylantiradi. Frontend tomonda openapi-typescript hujjatdan TypeScript tiplarini chiqaradi, orval yoki hey-api esa tayyor klient funksiyalarini. CI'da Spectral hujjat sifatini, oasdiff kabi vositalar esa mijozlarni buzadigan o'zgarishni ushlaydi.

Bu darsda

  • Zod sxemalari va registerPath dan OpenAPI 3.1 hujjatini yasaysiz va kanon natijasini o'qiysiz.
  • zod-to-openapi va Zod'ning o'z z.toJSONSchema() natijasi farqini bilasiz.
  • openapi-typescript chiqargan tiplarni frontend'da ishlatasiz va klient generatorlarini solishtirasiz.
  • Breaking change nima ekanini va uni avtomatik qanday aniqlashni tushuntirasiz.

Oldin bilishingiz kerak: OpenAPI 3.1 spetsifikatsiyasi, Zod 4 serverda, API versiyalash va deprecation.

1. Nega bu kerak?

Oldingi darsda openapi.yaml ni qo'lda yozdik. Endi vazifalar-api ga qaraylik: matn uchun "1 dan 100 gacha belgi" qoidasi qayerda yashaydi? Zod sxemasida (Zod 4 serverda). Qo'lda yozilgan YAML'da ham minLength: 1, maxLength: 100 bo'ladi. Ikki joy — ikki haqiqat. Kimdir 100 ni 200 qiladi, ikkinchisini unutadi.

Code-first yondashuv: haqiqat bitta — kod. Zod sxemasi so'rovni tekshiradi va hujjatga aylanadi. Hujjat build yoki ishga tushish paytida yasaladi, u kod bilan hech qachon ajralmaydi.

Shu darsda zanjirni to'liq ko'ramiz:

flowchart LR
  Z["Zod sxemalari"] --> G["zod-to-openapi"]
  G --> J["openapi.json"]
  J --> U["Swagger UI / Scalar"]
  J --> L["Spectral: lint"]
  J --> D["oasdiff: breaking change"]
  J --> T["openapi-typescript"]
  T --> F["Frontend tiplari"]

Kanon tajribasi (2026-10-27): zod-to-openapi 9.1.0, zod 4.6.7, openapi-typescript 7.13.0, Node 24.21.0. Swagger UI, Scalar, Redoc, orval, hey-api, Spectral va oasdiff — sinalmagan, ular hujjat bo'yicha (2026-10). To'liq hujjat va tiplar chiqishini biz kanon kodi bilan qayta ishga tushirib oldik.

2. zod-to-openapi bilan hujjat yasash

2.1 Sxemalarni ro'yxatga olish

@asteasolutions/zod-to-openapi Zod'ga .openapi() metodini qo'shadi va registry (ro'yxat) beradi: unga sxemalar, xavfsizlik sxemalari va marshrutlar yoziladi. Kanon kodi (telefon uchun bo'ldik):

js
import { z } from "zod";
import {
  OpenAPIRegistry,
  OpenApiGeneratorV31,
  extendZodWithOpenApi,
} from "@asteasolutions/zod-to-openapi";

extendZodWithOpenApi(z);
const reg = new OpenAPIRegistry();

const Vazifa = reg.register(
  "Vazifa",
  z.object({
    id: z.number().int().openapi({ example: 42 }),
    matn: z.string().min(1).max(100),
    bajarildi: z.boolean(),
    muddat: z.iso.datetime().nullable().optional(),
  }).openapi({ description: "Bitta vazifa" }),
);
const Problem = reg.register("Problem", z.object({
  type: z.string(),
  title: z.string(),
  status: z.number().int(),
  detail: z.string().optional(),
}));
reg.registerComponent("securitySchemes", "cookieAuth", {
  type: "apiKey", in: "cookie", name: "__Host-sessiya",
});

reg.register("Vazifa", sxema) — sxemani components/schemas/Vazifa ga yozadi va o'sha sxemani qaytaradi. Qaytgan Vazifa — oddiy Zod sxemasi: u bilan Vazifa.parse(body) qilish ham mumkin. Bitta obyekt — validatsiya va hujjat.

2.2 Marshrutni tasvirlash

js
reg.registerPath({
  method: "get",
  path: "/v1/vazifalar/{id}",
  summary: "Vazifani olish",
  security: [{ cookieAuth: [] }],
  request: {
    params: z.object({ id: z.coerce.number().int().positive() }),
  },
  responses: {
    200: {
      description: "OK",
      content: { "application/json": { schema: Vazifa } },
    },
    404: {
      description: "Topilmadi",
      content: { "application/problem+json": { schema: Problem } },
    },
  },
});

const generator = new OpenApiGeneratorV31(reg.definitions);
const spec = generator.generateDocument({
  openapi: "3.1.0",
  info: { title: "vazifalar-api", version: "3.0.0" },
});
console.log(JSON.stringify(spec).length, "bayt");

Kanon natijasi:

text
hujjat: 1141 bayt; Vazifa sxemasi: {"type":"object","properties":{"id":{"type":"integer","example":42},"matn":{"type":"string","minLength":1,"maxLength":100},"bajarildi":{"type":"boolean"},"muddat":{"type":["string","null"],"format":"date-time"}},"required":["id","matn","bajarildi"],"description":"Bitta vazifa"}

Zod'dagi har qoida JSON Schema'ga o'tdi:

Zod OpenAPI 3.1
z.number().int() "type": "integer"
.min(1).max(100) minLength, maxLength
.nullable() "type": ["string", "null"]
.optional() required ro'yxatida yo'q
z.iso.datetime() "format": "date-time"
.positive() (parametr) "exclusiveMinimum": 0

Oxirgi qatorni biz to'liq hujjatdan oldik — id parametri shunday chiqdi:

json
{
  "schema": { "type": "integer", "exclusiveMinimum": 0 },
  "required": true,
  "name": "id",
  "in": "path"
}

registerPath marshrutning o'zini yaratmaydi — u faqat tasvir. Express marshruti alohida yoziladi. Demak bitta xavf qoladi: marshrut kodda bor, registerPath da yo'q (yoki aksincha). Kichik loyihada buni kontrakt test ushlaydi (pastda). Fastify va NestJS (30-qism) marshrut va sxemani bitta joyda e'lon qiladi — bu muammo u yerda kamroq.

2.3 z.toJSONSchema() bilan farq

Zod 4 da JSON Schema'ga o'tkazish o'zida bor — z.toJSONSchema() (Zod 4 serverda). Unda nega alohida kutubxona? Biz o'sha matn va muddat maydonlarini ikkala yo'l bilan o'tkazdik (zod 4.6.7). z.toJSONSchema natijasi (uzun regex'ni … bilan qisqartirdik):

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "matn": { "type": "string", "minLength": 1, "maxLength": 100 },
    "muddat": {
      "anyOf": [
        { "type": "string", "format": "date-time", "pattern": "…" },
        { "type": "null" }
      ]
    }
  },
  "required": ["matn"],
  "additionalProperties": false
}

Uch farq:

  1. nullable — anyOf bilan (zod-to-openapi: type massivi). Ikkalasi ham to'g'ri JSON Schema, lekin ba'zi klient generatorlari anyOf dan noqulay tip yasaydi.
  2. additionalProperties: false — Zod z.object sukut bo'yicha ortiqcha kalitlarni tashlab yuboradi, toJSONSchema esa buni "ortiqcha kalit taqiqlangan" deb yozdi. zod-to-openapi bu kalitni qo'ymadi.
  3. pattern — sana uchun uzun regex qo'shildi; zod-to-openapi faqat format yozdi.

Asosiy farq esa bu jadvalda emas: toJSONSchema faqat sxema beradi. paths, parametrlar, javoblar, xavfsizlik va $ref bilan components — zod-to-openapi ishi. Bitta sxemani hujjatga qo'shish uchun toJSONSchema yetadi, butun API uchun — registry.

3. Hujjatni berish va ko'rsatish

Hujjat odatda ikki marshrutda: mashina uchun GET /openapi.json, odam uchun GET /docs. Express'da birinchisi bir qator:

js
app.get("/openapi.json", (_req, res) => res.json(spec));

/docs uchun uchta mashhur vosita (hujjat bo'yicha, sinalmagan):

Vosita Xususiyati
Swagger UI eng keng tarqalgan; "Try it out" — brauzerdan so'rov yuborish
Scalar zamonaviy ko'rinish, ichida so'rov yuborish va kod namunalari
Redoc uch ustunli o'qish uchun hujjat; so'rov yuborish yo'q

Uchalasi ham openapi.json ni o'qiydigan statik sahifa: CDN'dan skript va bitta HTML. Production'da /docs ni ochiq qoldirish — qaror: ichki API bo'lsa, faqat ichki tarmoqda yoki autentifikatsiya bilan. "Try it out" haqiqiy so'rov yuboradi — prod ma'lumotiga tegishi mumkin.

Diqqat: /docs sahifasi tashqi CDN'dan skript yuklasa, Helmet va xavfsizlik header'lari darsidagi CSP uni bloklaydi. CSP'ni butun API uchun bo'shatmang — faqat /docs marshruti uchun alohida siyosat bering yoki skriptni o'zingizda saqlang.

4. Hujjatdan TypeScript tiplari

4.1 openapi-typescript

Frontend tomonda hujjatdan tip chiqaramiz. openapi-typescript — hujjatni o'qib, .d.ts fayl yozadi; runtime kodi yo'q. Kanon chiqishidan parcha:

text
openapi-typescript → TS (parcha): Vazifa: { matn: string; muddat?: string | null;

To'liq natijada (biz ishga tushirdik, openapi-typescript 7.13.0) components qismi:

ts
export interface components {
  schemas: {
    /** @description Bitta vazifa */
    Vazifa: {
      /** @example 42 */
      id: number;
      matn: string;
      bajarildi: boolean;
      /** Format: date-time */
      muddat?: string | null;
    };
    // Problem — xuddi shunday
  };
}

muddat?: string | null — ? — optional, | null — nullable. Ikki holat frontend'da ham ajralib turibdi. integer TS'da number bo'ldi — TS'da butun son tipi yo'q. date-time — string: tip Date emas, chunki JSON'da sana satr bo'lib keladi.

paths qismida esa har amalning parametrlari va javoblari:

ts
export interface paths {
  "/v1/vazifalar/{id}": {
    get: {
      parameters: { path: { id: number } };
      responses: {
        200: {
          content: {
            "application/json": components["schemas"]["Vazifa"];
          };
        };
        // 404 — Problem
      };
    };
  };
}

(Bu ikki blokni o'qish uchun qisqartirdik: generator har amalga query?: never, header?: never kabi bo'sh qismlarni ham yozadi.)

Frontend'da ishlatish:

ts
import type { components } from "./api-tiplari";

type Vazifa = components["schemas"]["Vazifa"];

function muddatMatni(v: Vazifa): string {
  return v.muddat ?? "muddatsiz";
}

19-qismda Vazifa tipini qo'lda yozgandik. Endi u backend sxemasidan keladi. Backend bajarildi ni o'chirsa, frontend build'i tip xatosi bilan yiqiladi — ishlab chiqarishda emas.

4.2 Klient generatorlari

Tiplar bor, fetch chaqiruvlari esa hali qo'lda. Keyingi qadam — tayyor klient. Uch yo'l (hujjat bo'yicha, sinalmagan):

Vosita Nima yasaydi Qachon
openapi-fetch tiplar ustida yupqa fetch o'rami, kod generatsiyasiz eng kichik, openapi-typescript bilan juft
orval funksiyalar, TanStack Query hook'lari, MSW mock'lari React + TanStack Query loyihasi
hey-api (@hey-api/openapi-ts) SDK funksiyalar, Zod sxemalar, plaginlar to'liq SDK kerak bo'lsa

openapi-fetch g'oyasi: client.GET("/v1/vazifalar/{id}", { params: { path: { id: 7 } } }) — yo'l satri ham, parametr ham, javob ham tipli. Yo'lni xato yozsangiz — tip xatosi. orval esa useVazifaOlish(7) kabi hook'ni o'zi yozadi (TanStack Query I bilan). Tanlov — loyihada qaysi data-fetching usuli ishlatilishiga bog'liq.

Generatsiya qilingan kod qo'lda tahrirlanmaydi — keyingi generatsiya uni o'chiradi. Odatda npm run api:tiplar skripti va CI'da "generatsiya qilingan fayl yangimi?" tekshiruvi.

5. Breaking change

5.1 Nima buzadi, nima buzmaydi

Breaking change — mavjud mijozni buzadigan o'zgarish (API versiyalash va deprecation). Qoida yo'nalishga bog'liq: server javobda va'da qiladi, so'rovda talab qiladi.

O'zgarish Javobda So'rovda
Maydon olib tashlandi buzadi (mijoz o'qiydi) buzmaydi
Yangi ixtiyoriy maydon buzmaydi buzmaydi
Yangi majburiy maydon buzmaydi buzadi (eski mijoz yubormaydi)
Tur o'zgardi buzadi buzadi
null ga ruxsat berildi buzadi (mijoz null kutmaydi) buzmaydi

Tekshirib ko'ring: Javobdagi id turini integer dan string ga o'zgartirdik (UUID'ga o'tish). Bu buzadimi?

Javob

Ha, ikkala yo'nalishda ham. Frontend id ni son deb solishtiradi (v.id === 7), URL'ga qo'yadi, saralaydi — hammasi o'zgaradi. Bunday o'tish odatda yangi maydon (uuid) qo'shish, mijozlarni unga ko'chirish va eski maydonni deprecation bilan keyinroq olib tashlash orqali qilinadi — Zero-downtime: expand-contract g'oyasining API'dagi ko'rinishi.

Kanon oddiy tekshiruvni yozgan: hujjat nusxasida bajarildi ni o'chirib, muddat ni majburiy qilgan:

text
oddiy breaking-change tekshiruvi: javobdan maydon olib tashlandi: bajarildi; yangi majburiy maydon: muddat (so'rovda bo'lsa — breaking) | (oasdiff — Go binar, sinalmagan)

5.2 O'zimiz yozamiz

Kanon tekshiruvini sxemaning qayerda ishlatilishini hisobga oladigan qilib yozamiz:

js
function farqlar(eski, yangi, joy) {
  const x = [];
  for (const [k, s] of Object.entries(eski.properties)) {
    const ys = yangi.properties[k];
    if (!ys) {
      if (joy === "javob") x.push(`BUZADI: ${k} olib tashlandi`);
      continue;
    }
    const et = [s.type].flat().join("|");
    const yt = [ys.type].flat().join("|");
    if (et !== yt) {
      const nullQoshildi = yt === `${et}|null`;
      if (joy === "javob" || !nullQoshildi) {
        x.push(`BUZADI: ${k} turi ${et} → ${yt}`);
      }
    }
  }
  for (const k of yangi.required ?? []) {
    if (!(eski.required ?? []).includes(k) && joy === "sorov") {
      x.push(`BUZADI: ${k} endi majburiy`);
    }
  }
  return x.length ? x : ["xavfsiz"];
}

const v1 = {
  properties: {
    matn: { type: "string" },
    bajarildi: { type: "boolean" },
    muddat: { type: "string" },
  },
  required: ["matn"],
};
const v2 = {
  properties: {
    matn: { type: "string" },
    muddat: { type: ["string", "null"] },
    teg: { type: "string" },
  },
  required: ["matn", "teg"],
};
console.log("javob:", farqlar(v1, v2, "javob"));
console.log("so'rov:", farqlar(v1, v2, "sorov"));

Konsolda:

text
javob: [
  'BUZADI: bajarildi olib tashlandi',
  'BUZADI: muddat turi string → string|null'
]
so'rov: [ 'BUZADI: teg endi majburiy' ]

Bir xil o'zgarish ikki joyda ikki xil baho oldi. bajarildi olib tashlandi — javobda buzadi, so'rovda emas. muddat ga null qo'shildi — javobda buzadi (frontend v.muddat.slice() qilsa yiqiladi), so'rovda emas. teg majburiy bo'ldi — so'rovda buzadi.

Haqiqiy vositalar (oasdiff — Go'da yozilgan, Node paketi emas; openapi-diff va boshqalar) yuzlab qoidani tekshiradi: yo'l o'chirilishi, status kod o'zgarishi, enum qiymatining olib tashlanishi, parametr joyining o'zgarishi. Ularning vazifasi bitta: pull request'da "bu o'zgarish mijozlarni buzadi" deb CI'ni to'xtatish. Buzish kerak bo'lsa — yangi versiya (/v2/) va eski versiyani bosqichma-bosqich yopish.

6. Hujjat sifatini tekshirish: Spectral

Hujjat to'g'ri formatda bo'lishi bilan yaxshi bo'lishi boshqa narsa. Spectral (Stoplight) — OpenAPI uchun linter, ESLint kabi (hujjat bo'yicha, sinalmagan). Uning sukut qoidalari to'plami spectral:oas quyidagilarni ushlaydi: operationId yo'qligi yoki takrorlanishi, description siz amallar, ishlatilmagan components, servers yo'qligi. O'z qoidangizni ham yozasiz: "har amalda 4xx javob bo'lsin", "yo'llar kebab-case'da".

CI'dagi tartib odatda shunday:

  1. openapi.json ni generatsiya qilish (testlar bilan birga).
  2. Spectral — xato bo'lsa CI to'xtaydi.
  3. main dagi hujjat bilan diff — breaking bo'lsa, PR'da ogohlantirish yoki to'xtatish.
  4. Frontend tiplarini qayta generatsiya qilish va build.

CI'ni 34-qismda quramiz. Hozircha bu bosqichlarni lokal npm run check skriptiga qo'shish ham yetadi.

7. Kontrakt test: hujjat va javob mosmi?

Code-first'da ham hujjat va haqiqiy javob ajralishi mumkin: registerPath da 200 javob Vazifa, handler esa { vazifa: {...} } qaytaradi. Buni test ushlaydi. G'oya: integratsiya testida javobni hujjatdagi sxema bilan tekshirish. Code-first'da bu ayniqsa oson — sxema Zod'da bor:

js
test("GET /v1/vazifalar/:id javobi shartnomaga mos", async () => {
  const r = await request(app).get("/v1/vazifalar/1").set(kuki);
  assert.equal(r.status, 200);
  assert.ok(Vazifa.safeParse(r.body).success, "javob Vazifa emas");
});

Supertest bilan HTTP integration test darsidagi naqsh — faqat oxirgi qator yangi. Spec-first loyihada xuddi shu ishni Ajv hujjatdagi JSON Schema bilan qiladi.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
.openapi is not a function extendZodWithOpenApi(z) chaqirilmagan ilova boshida bir marta
Sxema hujjatda inline, $ref yo'q reg.register emas, oddiy Zod sxemasi nomli sxemalarni ro'yxatga oling
Marshrut bor, hujjatda yo'q registerPath unutilgan kontrakt test yoki marshrut ro'yxatini solishtirish
Frontend tiplari eski generatsiya qo'lda, unutilgan npm run skripti va CI tekshiruvi
Generatsiya qilingan faylga tuzatish yo'qoldi qo'lda tahrirlangan sxemani manbada tuzating
/docs bo'sh oq sahifa CSP tashqi skriptni blokladi /docs uchun alohida siyosat

9. Mashqlar

Mashqlar kurs/mashqlar/29/58-code-first/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Zod'ga .openapi() qo'shadigan chaqiruv —
  • Hujjatdan faqat TS tiplarini chiqaradigan vosita — openapi-
  • .optional() + .nullable() dan chiqqan TS tipi — muddat?: string |
  • Javobdan maydonni olib tashlash — change
  • OpenAPI uchun linter —
Yechim
  • extendZodWithOpenApi — extendZodWithOpenApi(z).
  • typescript — openapi-typescript, runtime kodi yo'q.
  • null — ? optional, | null nullable.
  • breaking — mavjud mijoz o'qiydigan maydon yo'qoldi.
  • Spectral — spectral:oas qoidalari.

2-mashq (o'rta): kichik tip generatori

tipYasa(nom, sxema) funksiyasini yozing — openapi-typescript ning eng kichik varianti. U type Nom = { ... }; satrini qaytarsin: required da bo'lmagan maydonga ?; integer/number → number, string → string, boolean → boolean, "null" → null, turlar massivi → | bilan; enum bo'lsa — qiymatlar "a" | "b"; type: "array" — items turi va [].

Yechim
js
const TS = {
  integer: "number", number: "number", string: "string",
  boolean: "boolean", null: "null",
};

function turTS(s) {
  if (s.enum) return s.enum.map((q) => JSON.stringify(q)).join(" | ");
  if (s.type === "array") return `${turTS(s.items)}[]`;
  return [s.type].flat().map((t) => TS[t] ?? "unknown").join(" | ");
}

function tipYasa(nom, sxema) {
  const majburiy = new Set(sxema.required ?? []);
  const qatorlar = Object.entries(sxema.properties).map(([k, s]) => {
    const belgi = majburiy.has(k) ? "" : "?";
    return `  ${k}${belgi}: ${turTS(s)};`;
  });
  return `type ${nom} = {\n${qatorlar.join("\n")}\n};`;
}

console.log(tipYasa("Vazifa", {
  type: "object",
  properties: {
    id: { type: "integer" },
    matn: { type: "string" },
    ustuvorlik: { enum: ["past", "orta", "yuqori"] },
    teglar: { type: "array", items: { type: "string" } },
    muddat: { type: ["string", "null"] },
  },
  required: ["id", "matn", "ustuvorlik"],
}));

Konsolda:

text
type Vazifa = {
  id: number;
  matn: string;
  ustuvorlik: "past" | "orta" | "yuqori";
  teglar?: string[];
  muddat?: string | null;
};

teglar — string[], ustuvorlik — literal tiplar birlashmasi: frontend "juda" deb yozsa, tip xatosi. Haqiqiy generator yana $ref (components["schemas"]["X"]), oneOf/allOf, additionalProperties (Record<string, …>) va izohlarni (/** @description */) biladi.

3-mashq (qiyin): enum o'zgarishlari

Darsdagi farqlar ga enum qoidalarini qo'shing. Enum'dan qiymat olib tashlansa — so'rovda buzadi (eski mijoz uni yuborishi mumkin), javobda buzmaydi. Enum'ga qiymat qo'shilsa — javobda buzadi (eski mijoz switch da yangi qiymatni bilmaydi), so'rovda buzmaydi. Faqat enum qismini yozing: enumFarq(eski, yangi, joy) — eski va yangi — maydon sxemalari.

Yechim
js
function enumFarq(eski, yangi, joy) {
  const e = new Set(eski.enum ?? []);
  const y = new Set(yangi.enum ?? []);
  const ketdi = [...e].filter((q) => !y.has(q));
  const keldi = [...y].filter((q) => !e.has(q));
  const x = [];
  if (ketdi.length && joy === "sorov") {
    x.push(`BUZADI: olib tashlandi ${ketdi.join(", ")}`);
  }
  if (keldi.length && joy === "javob") {
    x.push(`BUZADI: qo'shildi ${keldi.join(", ")}`);
  }
  return x.length ? x : ["xavfsiz"];
}

const eski = { enum: ["past", "orta", "yuqori"] };
const yangi = { enum: ["past", "orta", "yuqori", "shoshilinch"] };
const kichik = { enum: ["past", "yuqori"] };
console.log(enumFarq(eski, yangi, "javob"));
console.log(enumFarq(eski, yangi, "sorov"));
console.log(enumFarq(eski, kichik, "sorov"));
console.log(enumFarq(eski, kichik, "javob"));

Konsolda:

text
[ "BUZADI: qo'shildi shoshilinch" ]
[ 'xavfsiz' ]
[ 'BUZADI: olib tashlandi orta' ]
[ 'xavfsiz' ]

Javobdagi yangi enum qiymati ko'pincha "kichik o'zgarish" deb o'tkazib yuboriladi — lekin mobil ilova eski versiyasida switch ning default qismi bo'lmasa, ekran bo'sh qoladi. Shuning uchun ba'zi jamoalar javobdagi enum'larni "kengayishi mumkin" deb hujjatlaydi va mijozdan noma'lum qiymatni ko'tarishni talab qiladi.

10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

Kurs rejasi bo'yicha OpenAPI vazifalar-api ga qo'shilmaydi. Kanon zanjirni tajribada yurgizdi: Zod → zod-to-openapi → 1 141 baytlik hujjat → openapi-typescript tiplari → oddiy breaking-change tekshiruvi. Loyihada esa hujjat 30-qismda NestJS'ning Swagger moduli bilan avtomatik paydo bo'ladi — u ham code-first, faqat Zod o'rniga dekoratorlar (yoki Zod adapteri). Bu darsdagi g'oyalar — operationId, tip generatsiyasi, breaking change — o'sha yerda to'g'ridan ishlatiladi.

O'zingiz sinab ko'ring: alohida tajriba papkasida npm i zod@4.6.7 @asteasolutions/zod-to-openapi@9.1.0 openapi-typescript@7.13.0. vazifalar-api dagi Zod sxemalaringizni nusxalab, ikkita amal (GET va POST) uchun hujjat yasang. npx openapi-typescript openapi.json -o api-tiplari.d.ts bilan tiplarni chiqarib, frontend'dagi qo'lda yozilgan Vazifa tipi bilan solishtiring.

11. Real ishda

  • TS monorepo (Next.js frontend + Express/Fastify backend) — code-first hujjat va generatsiya qilingan tiplar eng ko'p uchraydigan juftlik.
  • Mobil jamoa — Kotlin/Swift klientlarini OpenAPI Generator bilan yasaydi; hujjat sifati ular uchun hayotiy.
  • Ommaviy API (to'lov, logistika) — Scalar yoki Redoc sahifasi, Spectral qoidalari, har relizda diff.
  • NestJS — @nestjs/swagger dekoratorlardan hujjat (30-qism).
  • Intervyuda: "Code-first va spec-first farqi?", "Frontend tiplarini backend bilan qanday sinxron ushlaysiz?", "Qaysi o'zgarishlar breaking?"

Xulosa

  • Code-first: Zod sxemasi = validatsiya + hujjat; reg.register sxemani components ga, registerPath amalni paths ga yozadi.
  • z.toJSONSchema — bitta sxema uchun; butun API hujjati uchun registry kerak; natijalari biroz farqli (anyOf, additionalProperties).
  • /openapi.json — mashina uchun, /docs (Swagger UI, Scalar, Redoc) — odam uchun; CSP va prod'dagi "Try it out" ni unutmang.
  • openapi-typescript — tiplar; openapi-fetch, orval, hey-api — klient; generatsiya qilingan kod qo'lda tahrirlanmaydi.
  • Breaking change yo'nalishga bog'liq: javobdan olib tashlash, so'rovda yangi majburiy maydon.
  • Spectral, oasdiff va kontrakt test — CI'dagi himoya; ular kanonda sinalmagan.

Keyingi dars: GraphQL asoslari — mijoz o'ziga kerakli maydonlarni o'zi so'raydigan API: sxema, Query, Mutation va resolver'lar.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!