IlmHamroh
JavaScript Full-stack/30-qism. NestJS37/65-dars16 daqiqa
Mundarija (28)

NestJS Swagger: @nestjs/swagger bilan OpenAPI hujjati, DTO va Zod sxemalari

Qisqacha: @nestjs/swagger Nest controller'laridan OpenAPI hujjatini avtomatik yasaydi: DocumentBuilder — sarlavha va versiya, SwaggerModule.createDocument(app, …) — yo'llar, metodlar va sxemalar, SwaggerModule.setup("docs", …) — Swagger UI sahifasi va JSON. Tana sxemasini Nest DTO klassidan oladi (@ApiProperty yoki CLI plugini). NestJS 12 da esa to'g'ridan Zod sxemasidan ham. vazifalar-api da hujjat konverter'siz Zod'dan chiqdi, lekin ikki tuzoq topildi: @All() yo'llari hujjatni buzadi, versiyalar esa operationId ga [0] qo'shadi.

Bu darsda

  • DocumentBuilder, createDocument va setup bilan /docs va /docs-json ni ulaysiz.
  • @ApiProperty, CLI plugini, @ApiResponse va @ApiBearerAuth qanday sxema berishini haqiqiy JSON'da ko'rasiz.
  • Zod sxemalari qanday qilib konverter'siz OpenAPI'ga aylanishini tushuntirasiz.
  • @All() va API versiyalari keltirib chiqargan haqiqiy xatolarni topasiz va tuzatasiz.
  • vazifalar-api ga hujjatni qo'shib, test bilan himoya qilasiz va commit qilasiz.

Oldin bilishingiz kerak: OpenAPI 3.1 spetsifikatsiyasi, Code-first OpenAPI va klient generatsiyasi, Mapped types va nested validatsiya, Zod va Standard Schema NestJS'da, Repository naqshi va qatlamlar.

O'tgan darsda vazifalar-api ning ichki tuzilishini — qatlamlar va ombor shartnomasini — yig'dik. Endi tashqi shartnoma: API'dan foydalanadigan odam yoki dastur nimani yuborishi va nima olishini qayerdan biladi?

1. Nega bu kerak?

29-qismda OpenAPI'ni o'rgandik: API'ning mashina o'qiydigan tavsifi — yo'llar, metodlar, tana va javob sxemalari (OpenAPI 3.1). U yerda "code-first" yo'lni ham ko'rdik: hujjat qo'lda yozilmaydi, koddan yasaladi (Code-first OpenAPI). Qo'lda yozilgan hujjat birinchi o'zgarishdayoq koddan orqada qoladi.

vazifalar-api da hozircha faqat API.md bor — matn. Frontend dasturchi yoki mobil ilova jamoasi undan so'rovni qo'lda tuzadi va matn uzunligi chegarasini xato javobidan bilib oladi.

O'xshatish: restoran menyusi. Oshxona ishlayapti, lekin menyu yo'q — mijoz har safar ofitsiantdan "osh bormi, necha pul?" deb so'raydi. OpenAPI — avtomatik yangilanadigan menyu: oshxonaga yangi taom qo'shilsa, menyuda ham paydo bo'ladi.

Nest'da bu ayniqsa oson: controller'lar, dekoratorlar va metama'lumot hammasi bor (Reflector va metadata) — hujjat uchun kerak ma'lumotning ko'pi allaqachon kodda.

2. Asosiy uchlik: DocumentBuilder, createDocument, setup

ts
const sozlama = new DocumentBuilder()
  .setTitle("Bahor menyusi")
  .addBearerAuth()
  .build();
const hujjat = SwaggerModule.createDocument(app, sozlama);
SwaggerModule.setup("docs", app, hujjat, {
  jsonDocumentUrl: "docs-json",
});
Qism Nima qiladi
DocumentBuilder hujjat "qopqog'i": sarlavha, tavsif, versiya, xavfsizlik sxemalari
createDocument(app, …) ilovadagi controller'larni aylanib, paths va components ni yig'adi
setup("docs", …) /docs — Swagger UI sahifasi, /docs-json — xom JSON

createDocument ga app beriladi — u app.init() dan keyin chaqirilmaydi, lekin modullar yig'ilgan bo'lishi kerak. Kanonda u ilovaYarat ichida, app.init() dan oldin.

3. Tana sxemasi qayerdan keladi? Nest standarti

3.1 @ApiProperty

Nest hujjatidagi yo'l — DTO klassi (DTO va class-validator) va har maydonda @ApiProperty. Demo loyihada sinadik (biz ishga tushirdik: @nestjs/swagger 12.0.2, NestJS 12.2.0):

ts
class YangiTaom {
  @ApiProperty({ example: "Osh", minLength: 1, maxLength: 50 })
  nom!: string;
  @ApiProperty({ example: 35000, description: "narx, so'mda" })
  narx!: number;
}

@ApiBearerAuth()
@Controller("taomlar")
class TaomlarController {
  @Post()
  @ApiResponse({ status: 201, type: Taom })
  @ApiResponse({ status: 409, description: "Bunday taom bor" })
  yarat(@Body() b: YangiTaom): Taom {
    return { id: 1, ...b, bor: true };
  }
}

Hujjatdan chiqqan qismlar:

text
POST operationId: TaomlarController_yarat
security: [{"bearer":[]}]
responses: ["201","409"]
YangiTaom: {"type":"object","properties":{"nom":{"type":"string","example":"Osh","minLength":1,"maxLength":50},"narx":{"type":"number","example":35000,"description":"narx, so'mda"}},"required":["nom","narx"]}
securitySchemes: {"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}
  • YangiTaom — @ApiProperty dagi hamma narsa sxemaga o'tdi: tur (string — TypeScript turidan, metama'lumot orqali), example, chegaralar. required — ikkala maydon, chunki ular ixtiyoriy (?) emas.
  • responses — @ApiResponse lar: 201 va 409. Nest ularni o'zi bilmaydi — siz yozasiz.
  • security va securitySchemes — @ApiBearerAuth() + addBearerAuth(): "bu yo'l Authorization: Bearer … talab qiladi". Swagger UI'da qulf belgisi va token kiritish oynasi paydo bo'ladi. Auth Nest'ga ko'chganda (30-qism oxirida) bu mavzuga qaytamiz.
  • operationId — sukutda Klass_metod: TaomlarController_yarat. Klient generatori shu nomdan funksiya yasaydi.

3.2 CLI plugini — dekoratorsiz

Har maydonga @ApiProperty yozish charchatadi va tur ikki marta yoziladi. @nestjs/swagger ning CLI plugini buni build paytida qiladi: TypeScript turlari va izohlarini o'qib, metama'lumotni o'zi qo'shadi. nest-cli.json da:

json
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/swagger",
        "options": { "introspectComments": true }
      }
    ]
  }
}

Dekoratorsiz DTO:

ts
// taom.dto.ts — dekoratorsiz DTO: sxemani CLI plugini to'ldiradi
export class Taom {
  id!: number;
  nom!: string;
  narx!: number;
  /** menyuda bormi */
  bor?: boolean;
}

Natija (biz ishga tushirdik):

text
Taom: {"type":"object","properties":{"id":{"type":"number"},"nom":{"type":"string"},"narx":{"type":"number"},"bor":{"type":"boolean","description":"menyuda bormi"}},"required":["id","nom","narx"]}

bor? — ixtiyoriy, required da yo'q. /** menyuda bormi */ izohi description bo'ldi — introspectComments ishi. Lekin sinovda ikki tuzoq chiqdi:

  • Plugin o'chiq bo'lsa — xuddi shu klass: Taom: {"type":"object","properties":{}}. Sxema bo'm-bo'sh, xato yo'q.
  • Fayl nomi. Avval Taom klassini t-swagger.ts faylida yozdik — plugin yoqilgan bo'lsa ham natija bo'sh edi. Klassni taom.dto.ts ga ko'chirgach to'ldi. Plugin sukutda faqat .dto.ts va .entity.ts bilan tugaydigan fayllarni o'qiydi (dtoFileNameSuffix sozlamasi).

Ikkalasi ham jim tuzoq: hujjat yasaladi, faqat ichi bo'sh. Tekshiruv — testda sxemani o'qish (loyiha qadamida shunday qilamiz).

Tekshirib ko'ring: CLI plugini nest build paytida ishlaydi. Testlar Vitest'da, build'siz yuradi. Plugin qo'shgan sxema testda ko'rinadimi?

Javob

Odatda yo'q: Vitest TypeScript'ni o'zi o'giradi (Vite), Nest CLI'ning plugin'i u yerda ishlamaydi — metama'lumot qo'shilmaydi. Plugin'ga tayangan hujjat testda bo'sh chiqishi mumkin. Biz buni sinamadik (biz nest build natijasini yurgizdik), lekin sabab aniq: plugin — CLI build bosqichining bir qismi. Kanon bunday muammoga duch kelmaydi — sxema Zod'dan, ish vaqtida olinadi.

4. Kanon yo'li: Zod sxemasidan, konverter'siz

4.1 Standard JSON Schema

vazifalar-api da DTO klasslari yo'q — kirish ma'lumoti Zod sxemalari bilan tekshiriladi (Zod va Standard Schema NestJS'da). OpenAPI uchun ham shu sxemalarni ishlatish mumkinmi?

Kanon topgan javob — ha, va hech qanday qo'shimcha paketsiz. Zod 4.6 sxemalarida ~standard.jsonSchema bor — Standard JSON Schema interfeysi: sxema o'zini JSON Schema ko'rinishida bera oladi. NestJS 12 swagger moduli shu interfeysni taniydi: @Body({ schema: … }) dagi Zod sxemasini to'g'ridan OpenAPI 3.0 ga aylantiradi. Nest hujjatida tavsiya qilingan zod-openapi va konverter kerak bo'lmadi (kanon o'lchovi, IlmHamroh, 2026-10).

flowchart LR
  Z["Zod sxemasi (sxema.ts)"] --> S["~standard.jsonSchema"]
  S --> N["SwaggerModule.createDocument"]
  C["@Controller, @Post, @Query"] --> N
  N --> J["/docs-json (OpenAPI 3.0)"]
  J --> U["/docs (Swagger UI)"]
  J --> G["klient generatori"]

Chap tomonda — ikki manba: Zod sxemalari (tana va query) va controller dekoratorlari (yo'l, metod). Ikkalasi ham kodda allaqachon bor edi. O'ng tomonda — iste'molchilar: odam (UI) va dastur (generator).

4.2 Natija

Loyiha qadamidagi o'zgarishlardan keyin /docs-json (biz ishga tushirdik; har yo'l uchun metod va operationId):

text
openapi: 3.0.0 | info: {"title":"vazifalar-api","description":"vazifalar ilovasining REST API'si (v1). Xatolar — application/problem+json (RFC 9457). Batafsil: API.md","version":"2.0.0","contact":{}}
/v1/vazifalar: get=royxat, post=yarat, put=almashtirHammasi
/v1/vazifalar/toplam: post=toplam
/v1/vazifalar/{id}: get=ol, put=almashtir, patch=yangila, delete=ochir
/v1/teglar: get=teglar
/v1/eksportlar: post=boshla
/v1/eksportlar/{id}: get=holat
/v1/eksportlar/{id}/fayl: get=fayl
POST tana: {"type":"object","properties":{"matn":{"type":"string","minLength":1,"maxLength":100},"bajarildi":{"type":"boolean"}},"required":["matn"],"additionalProperties":false}
GET /docs → 200 text/html; charset=utf-8 swagger-ui bor

POST tana qatoriga qarang: minLength: 1, maxLength: 100, required: ["matn"], additionalProperties: false — bularning hammasi 25-qismdagi YangiVazifaSxemasi dan (z.string().min(1).max(100), z.strictObject). Hech bir dekorator yozilmadi. Query parametrlari ham Zod'dan keldi: limit — pattern: ^\d{1,6}$, teg — anyOf (bitta satr yoki massiv), cursor — maxLength: 1000.

Bir manbadan ikki natija: Zod sxemasi so'rovni tekshiradi va hujjatlaydi. Sxema o'zgarsa — ikkalasi birga o'zgaradi.

5. Tajriba topgan ikki xato

5.1 @All() hujjatni egallab oldi

Controller'lar darsida 405 ni qaytarish uchun @All() yordamchilarini yozgandik: aniq metodlardan keyin turadi va boshqa metodga 405 Method Not Allowed beradi. HTTP'da to'g'ri ishlaydi. Lekin swagger uchun @All() — "hamma metod". Tuzatishlarsiz hujjat (biz ishga tushirdik, qisqartirilgan):

text
/v1/vazifalar: get=VazifalarController_toplamMetodYoq_get, post=VazifalarController_toplamMetodYoq_post, put=VazifalarController_toplamMetodYoq_put, delete=VazifalarController_toplamMetodYoq_delete, patch=VazifalarController_toplamMetodYoq_patch, options=VazifalarController_toplamMetodYoq_options, head=VazifalarController_toplamMetodYoq_head, search=VazifalarController_toplamMetodYoq_search
/v1/teglar: get=TeglarController_metodYoq_get, post=TeglarController_metodYoq_post, put=TeglarController_metodYoq_put, delete=TeglarController_metodYoq_delete, patch=TeglarController_metodYoq_patch, options=TeglarController_metodYoq_options, head=TeglarController_metodYoq_head, search=TeglarController_metodYoq_search

/v1/vazifalar uchun get ham, post ham — toplamMetodYoq ga tegishli bo'lib qoldi. Haqiqiy royxat va yarat hujjatdan yo'qoldi: @All() ning sakkizta metodi (hatto search ham) ularning ustidan yozdi. Kanon buni NestJS 12.2.0 va @nestjs/swagger 12.0.2 bilan haqiqiy xulq sifatida qayd etgan.

Tuzatish — yordamchini hujjatdan chiqarish:

ts
@ApiExcludeEndpoint() // 405 yordamchisi — hujjatda haqiqiy
@All()                // metodlarni ustidan yozmasin
toplamMetodYoq(@Req() req: Request): never {
  throw new MetodYoqXatosi(req.method, "GET, HEAD, POST, PUT");
}

5.2 Versiyalar: [0] va ikki nusxa

Xuddi shu tuzatishsiz hujjatda boshqa narsa ham bor:

text
/v1/eksportlar: post=EksportController_boshla[0]_v1
/eksportlar: post=EksportController_boshla[1]

Bootstrap darsida API versiyasini defaultVersion: ["1", VERSION_NEUTRAL] deb sozlagandik: har marshrut ikki joyda — /v1/... va eskirgan versiyasiz /... (API versiyalash). Swagger buni ikki alohida yo'l deb ko'rdi va operationId ga massiv indeksini ([0], [1]) qo'shdi. Natijada klient generatori EksportControllerBoshla0V1 kabi funksiya nomlari yasaydi, eskirgan yo'llar esa yangilari bilan teng turadi.

Tuzatish — hujjat.ts da ikki qator: operationIdFactory (faqat metod nomi, [N] siz) va paths filtri (faqat /v1/).

Diqqat: Ikkala xato ham jim: ilova ishlaydi, testlar o'tadi, /docs ochiladi. Faqat hujjat noto'g'ri. Shuning uchun loyiha qadamida hujjatning o'zini tekshiradigan test yoziladi — get, post, put aynan shu yo'lda bo'lishi va operationId: "yarat".

6. Hujumchi nigohi

/docs-json — API'ning to'liq xaritasi: har yo'l, har parametr, chegaralar. Hujumchi uchun bu razvedka: qaysi endpoint'lar bor, qaysi biri fayl qabul qiladi, qaysi parametr pattern siz.

  • Ichki API (faqat o'z frontend'ingiz) — prodakshnda /docs ni yopish yoki faqat auth bilan berish odatiy. Bu — muhit bo'yicha qaror (NODE_ENV), kod emas.
  • Ochiq API (boshqa jamoalar, hamkorlar) — hujjat ataylab ochiq; xavfsizlik hujjatni yashirishda emas, har endpoint'ning o'zida (OWASP API Security).
  • Eskirgan yo'llarni yashirish («Versiyalar» bo'limidagi filtr) ularni o'chirmaydi: /vazifalar hali ishlaydi. Hujjatda yo'q yo'l — himoyasiz yo'l emas, lekin unutilgan yo'l bo'lishi mumkin.

Kanon hozircha /docs ni har muhitda beradi: vazifalar-api ning yagona mijozi — o'z frontend'i, sir yo'q. Swagger UI aktivlari helmet CSP'si (script-src 'self') bilan mos keladi (aktivlar 200), lekin kanon sahifani haqiqiy brauzerda ochib ko'rmagan — buni o'zingiz qiling.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Sxema properties: {} @ApiProperty ham, plugin ham yo'q dekorator yoki plugin
Plugin yoqilgan, sxema bo'sh fayl .dto.ts emas fayl nomi yoki dtoFileNameSuffix
Haqiqiy metodlar hujjatda yo'q @All() ustidan yozgan @ApiExcludeEndpoint()
operationId da [0] versiyalar massivi operationIdFactory
Javoblar hujjatda yo'q @ApiResponse yozilmagan yozing (mashq)
/docs 404 setup app.init() dan keyin createDocument + setup — init dan oldin

8. Mashqlar

Mashqlar kurs/mashqlar/30/37-swagger/ da.

1-mashq (oson): qaysi dekorator?

To'ldiring:

  • Maydonga misol va chegara qo'shish — @
  • Yo'lni hujjatdan chiqarish — @ ()
  • Swagger UI qaysi yo'lda (kanon)? — /
  • CLI plugini sukutda o'qiydigan fayl qo'shimchasi — (va .entity.ts)
Yechim
  • ApiProperty — tur, example, minLength.
  • ApiExcludeEndpoint — @All() yordamchilari uchun.
  • docs — setup("docs", …); JSON — /docs-json.
  • .dto.ts — boshqa fayllardagi klasslarni plugin o'qimaydi.

2-mashq (o'rta): hujjatni tozalash

Kanondagi hujjatYarat ning ikki tuzatishini oddiy JS funksiya sifatida yozing: tozala(hujjat) — operationId lar oxiridagi [N] ni va Klass_ prefiksini olib tashlasin, faqat /v1/ bilan boshlanadigan yo'llarni qoldirsin. Kirish — «Versiyalar» bo'limidagi ko'rinishdagi kichik obyekt. Ishora: Object.fromEntries + Object.entries + filter (Object.* metodlari), replace(/\[\d+\](_v\d+)?$/, "").

Yechim
js
function idTozala(id) {
  return id
    .replace(/^[A-Za-z]+_/, "")
    .replace(/\[\d+\](_v\d+)?$/, "");
}

function tozala(hujjat) {
  const paths = Object.fromEntries(
    Object.entries(hujjat.paths)
      .filter(([yol]) => yol.startsWith("/v1/"))
      .map(([yol, metodlar]) => [
        yol,
        Object.fromEntries(Object.entries(metodlar).map(([m, a]) =>
          [m, { ...a, operationId: idTozala(a.operationId) }])),
      ]),
  );
  return { ...hujjat, paths };
}

const xom = {
  paths: {
    "/v1/eksportlar": {
      post: { operationId: "EksportController_boshla[0]_v1" },
    },
    "/eksportlar": {
      post: { operationId: "EksportController_boshla[1]" },
    },
    "/v1/eksportlar/{id}": {
      get: { operationId: "EksportController_holat[0]_v1" },
    },
  },
};
const toza = tozala(xom);
console.log(Object.keys(toza.paths));
console.log(toza.paths["/v1/eksportlar"].post.operationId);
console.log(toza.paths["/v1/eksportlar/{id}"].get.operationId);
console.log(Object.keys(xom.paths).length);

Konsolda:

text
[ '/v1/eksportlar', '/v1/eksportlar/{id}' ]
boshla
holat
3

Oxirgi qator: asl xom obyekt o'zgarmadi — tozala yangi obyekt qaytardi (Ichma-ich ma'lumotni immutable yangilash). Kanon esa createDocument ning o'z operationIdFactory sozlamasidan foydalanadi: [N] umuman qo'shilmaydi, prefiksni ham u olib tashlaydi ((_klass, metod) => metod).

3-mashq (qiyin): hujjat linteri

hujjatniTekshir(hujjat) yozing — muammolar ro'yxatini qaytarsin: (1) bitta yo'lda 7 tadan ortiq metod — "@All() belgisi"; (2) search metodi bor; (3) post/put/patch da requestBody yo'q; (4) javoblarda (responses) 2xx kodi yo'q. Har xabar: "<METOD> <yo'l>: <muammo>". Kirishda bitta "yaxshi" va bitta "buzilgan" yo'l bo'lsin. Ishora: Object.keys(r).some((k) => k.startsWith("2")).

Yechim
js
const YOZUVCHI = ["post", "put", "patch"];

function hujjatniTekshir(hujjat) {
  const muammolar = [];
  for (const [yol, metodlar] of Object.entries(hujjat.paths)) {
    const nomlar = Object.keys(metodlar);
    if (nomlar.length > 7) {
      const n = nomlar.length;
      muammolar.push(`* ${yol}: ${n} metod — @All() belgisi`);
    }
    for (const [m, amal] of Object.entries(metodlar)) {
      const nom = `${m.toUpperCase()} ${yol}`;
      if (m === "search") muammolar.push(`${nom}: search metodi`);
      if (YOZUVCHI.includes(m) && !amal.requestBody) {
        muammolar.push(`${nom}: requestBody yo'q`);
      }
      const r = amal.responses ?? {};
      if (!Object.keys(r).some((k) => k.startsWith("2"))) {
        muammolar.push(`${nom}: 2xx javob yo'q`);
      }
    }
  }
  return muammolar;
}

const metodlar = ["get", "post", "put", "delete", "patch",
  "options", "head", "search"];
const buzilgan = Object.fromEntries(metodlar.map((m) => [m, {}]));
const hujjat = {
  paths: {
    "/v1/vazifalar/{id}": {
      get: { responses: { 200: {} } },
      patch: { requestBody: {}, responses: { 200: {}, 412: {} } },
    },
    "/v1/teglar": buzilgan,
  },
};
for (const m of hujjatniTekshir(hujjat)) console.log(m);

Konsolda:

text
* /v1/teglar: 8 metod — @All() belgisi
GET /v1/teglar: 2xx javob yo'q
POST /v1/teglar: requestBody yo'q
POST /v1/teglar: 2xx javob yo'q
PUT /v1/teglar: requestBody yo'q
PUT /v1/teglar: 2xx javob yo'q
DELETE /v1/teglar: 2xx javob yo'q
PATCH /v1/teglar: requestBody yo'q
PATCH /v1/teglar: 2xx javob yo'q
OPTIONS /v1/teglar: 2xx javob yo'q
HEAD /v1/teglar: 2xx javob yo'q
SEARCH /v1/teglar: search metodi
SEARCH /v1/teglar: 2xx javob yo'q

Yaxshi yo'l (/v1/vazifalar/{id}) hech qanday xabar bermadi. Buzilgan /v1/teglar — «@All() hujjatni egallab oldi» bo'limidagi holat. E'tibor bering: kanonning o'z hujjati ham 4-qoidadan o'tmaydi — javob sxemalari (@ApiResponse) hali yozilmagan. Bu ochiq qadam: Vazifa uchun Zod sxemasi yozib, uni @ApiResponse({ status: 200, schema: … }) ga berish — o'z loyihangizda sinab ko'ring.

9. Loyiha qadami: /docs va /docs-json

9.1 Paket

bash
npm install --save-exact @nestjs/swagger@12.0.2

swagger-ui-dist (UI fayllari) u bilan birga keladi. Prod bog'liqliklar soni bittaga oshadi.

9.2 src/hujjat.ts

Faylning to'liq matni (kanon; izohlar va uzun qatorlar telefon uchun bo'lindi, ma'no o'zgarmagan):

ts
// hujjat.ts — OpenAPI hujjati (@nestjs/swagger). Kirish sxemalari —
// o'sha Zod sxemalari: Zod 4 Standard JSON Schema'ni qo'llaydi,
// Nest 12 swagger ularni konverter'siz OpenAPI 3.0 ga aylantiradi
// (DTO klasslari va CLI plugin kerak emas).
// /docs — Swagger UI, /docs-json — JSON
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import type { OpenAPIObject } from "@nestjs/swagger";
import type { INestApplication } from "@nestjs/common";

export function hujjatYarat(app: INestApplication): OpenAPIObject {
  const sozlama = new DocumentBuilder()
    .setTitle("vazifalar-api")
    .setDescription(
      "vazifalar ilovasining REST API'si (v1). Xatolar — " +
        "application/problem+json (RFC 9457). Batafsil: API.md",
    )
    .setVersion("2.0.0")
    .build();
  // operationId — metod nomi. Sukut —
  // "VazifalarController_royxat[0]_v1"
  // (versiyalar massivi uchun Nest "[0]" qo'shadi — olib tashlaymiz)
  const hujjat = SwaggerModule.createDocument(app, sozlama, {
    operationIdFactory: (_klass, metod) =>
      metod.replace(/\[\d+\]$/, ""),
  });
  // eskirgan versiyasiz yo'llar (/vazifalar) hujjatda yo'q, faqat /v1
  hujjat.paths = Object.fromEntries(
    Object.entries(hujjat.paths)
      .filter(([yol]) => yol.startsWith("/v1/")),
  );
  return hujjat;
}

export function hujjatniUla(app: INestApplication): void {
  const hujjat = hujjatYarat(app);
  SwaggerModule.setup("docs", app, hujjat, {
    jsonDocumentUrl: "docs-json",
  });
}

hujjatYarat alohida eksport qilingan — test va keyinroq klient generatsiyasi uni ilovani ishga tushirmasdan chaqira oladi.

9.3 src/ilova.ts

Import va bitta chaqiruv — enableVersioning dan keyin, app.init() dan oldin:

ts
import { hujjatniUla } from "./hujjat.ts";
// ...
  // OpenAPI: /docs (UI), /docs-json
  hujjatniUla(app);
  await app.init();

Tartib muhim: versiyalash yoqilgandan keyin — aks holda hujjatda /v1 yo'llari bo'lmaydi.

9.4 @ApiExcludeEndpoint() — to'rt joyda

src/vazifalar/vazifalar.controller.ts va teglar.controller.ts ga import: import { ApiExcludeEndpoint } from "@nestjs/swagger";. Har @All(...) dan oldin:

ts
  @ApiExcludeEndpoint() // 405 yordamchisi — hujjatni buzmasin
  @All()
  toplamMetodYoq(@Req() req: Request): never {

Jami to'rt yordamchi: toplamMetodYoq (@All()), toplamPostYoq (@All("toplam")), birMetodYoq (@All(":id")) va TeglarController.metodYoq. HTTP xulqi o'zgarmaydi — 405 avvalgidek ishlaydi.

9.5 Test

test/hujjat.test.ts (yangi fayl; formatlangan):

ts
// hujjat.test.ts — OpenAPI hujjati Zod sxemalaridan (Standard JSON
// Schema): kirish sxemalari hujjatda, faqat /v1,
// 405 yordamchilari yo'q
import { expect, test } from "vitest";
import request from "supertest";
import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";
import { httpIlova } from "./yordamchi.ts";

type Sxema = {
  properties?: Record<string, unknown>;
  required?: string[];
};
type Amal = {
  operationId: string;
  requestBody?: { content: Record<string, { schema: Sxema }> };
  parameters?: { name: string; in: string }[];
};
type Hujjat = {
  openapi: string;
  paths: Record<string, Record<string, Amal>>;
};

test("GET /docs-json: OpenAPI 3, faqat /v1, haqiqiy metodlar, " +
  "tana sxemasi Zod'dan", async () => {
  const http = await httpIlova({ ombor: new XotiraOmbori() });
  const r = await request(http).get("/docs-json").expect(200);
  const h = r.body as Hujjat;
  expect(h.openapi).toMatch(/^3\./);
  expect(Object.keys(h.paths).sort()).toEqual([
    "/v1/eksportlar", "/v1/eksportlar/{id}",
    "/v1/eksportlar/{id}/fayl",
    "/v1/teglar", "/v1/vazifalar", "/v1/vazifalar/toplam",
    "/v1/vazifalar/{id}",
  ]);
  const royxat = h.paths["/v1/vazifalar"] ?? {};
  expect(Object.keys(royxat).sort()).toEqual(["get", "post", "put"]);
  const bitta = h.paths["/v1/vazifalar/{id}"] ?? {};
  expect(Object.keys(bitta).sort())
    .toEqual(["delete", "get", "patch", "put"]);
  const post = royxat["post"];
  expect(post?.operationId).toBe("yarat");
  const tana = post?.requestBody?.content["application/json"];
  const sxema = tana?.schema;
  expect(sxema?.properties?.["matn"])
    .toMatchObject({ type: "string", minLength: 1, maxLength: 100 });
  expect(sxema?.required).toEqual(["matn"]);
  expect(royxat["get"]?.parameters?.map((p) => p.name))
    .toContain("cursor");
});

test("GET /docs — Swagger UI sahifasi", async () => {
  const http = await httpIlova({ ombor: new XotiraOmbori() });
  const r = await request(http).get("/docs").expect(200);
  expect(r.text).toMatch(/swagger-ui/i);
});

Birinchi test — «Tajriba topgan ikki xato» bo'limidagi ikkala xatoning regressiyasi: yo'llar ro'yxati (faqat /v1), metodlar (@All() sakkiztasi emas) va operationId: "yarat" ([0] siz). Tana sxemasining chegaralari — Zod'dan kelganining isboti.

9.6 Tekshiruv va commit

bash
npm run check
npm start

Biz holat nusxasida yurgizdik (biz ishga tushirdik, Postgres'siz): tip 0, lint 0, Vitest — 77 passed | 6 skipped (2 ta yangi test bilan), build o'tdi. Ishlayotgan ilovadan /docs-json va /docs — «Natija» bo'limidagi chiqish. Kanon to'liq muhitida (kanon o'lchovi, IlmHamroh, 2026-10): Vitest 120 passed | 1 skipped (121), build, Testcontainers 10/10, jonli API 9 so'rov uch saqlash rejimida.

Brauzerda http://127.0.0.1:3000/docs ni oching: chap tomonda yo'llar, POST /v1/vazifalar → "Try it out" — tana maydoni Zod chegaralari bilan.

bash
git add -A
M="feat: OpenAPI hujjati — /docs (Swagger UI), /docs-json;"
M+=" kirish sxemalari Zod'dan (Standard JSON Schema)"
git commit -m "$M"

10. Real ishda

  • Swagger UI — deyarli har Nest backend'ida /docs yoki /api da. Frontend va mobil jamoalar birinchi shu sahifani so'raydi.
  • Klient generatsiyasi — /docs-json dan openapi-typescript, orval kabi vositalar tipli klient yasaydi (Code-first OpenAPI). Kanon buni sinamagan; 29-qismdagi tajriba yo'li o'zgarmaydi.
  • Scalar va boshqa UI'lar — xuddi shu JSON'ni boshqacha ko'rsatadi. Hujjat bo'yicha ulanishi oson; biz sinamadik.
  • Intervyu: "Nest'da Swagger qanday ulanadi?", "CLI plugini nima qiladi?", "hujjat koddan orqada qolmasligini qanday ta'minlaysiz?" (javob: koddan yasalishi + hujjat testi).

Xulosa

  • DocumentBuilder → createDocument(app, …) → setup("docs", …): /docs UI, /docs-json JSON.
  • Nest standarti: DTO + @ApiProperty yoki CLI plugini (.dto.ts fayllari, nest build da); @ApiResponse, @ApiBearerAuth — qo'lda.
  • NestJS 12 Zod sxemalarini Standard JSON Schema orqali konverter'siz OpenAPI'ga aylantiradi — bitta sxema tekshiradi va hujjatlaydi.
  • @All() yordamchilari hujjatdagi haqiqiy metodlarni egallaydi — @ApiExcludeEndpoint().
  • Versiyalar massivi operationId ga [0] qo'shadi va eskirgan yo'llarni takrorlaydi — operationIdFactory va /v1/ filtri.
  • Hujjat xatolari jim — ularni test ushlaydi.

Keyingi dars: Keshlash: CacheModule — Nest'ning CacheModule va CacheInterceptor ini 27-qismdagi KeshliOmbor bilan solishtiramiz.

Manbalar

  • NestJS hujjati — OpenAPI (Introduction, Types and parameters, CLI plugin, Security, Operations): https://docs.nestjs.com/openapi/introduction
  • NestJS hujjati — CLI plugin (dtoFileNameSuffix, introspectComments): https://docs.nestjs.com/openapi/cli-plugin
  • Standard JSON Schema: https://standardschema.dev/
  • OpenAPI Specification 3.0: https://spec.openapis.org/oas/v3.0.3
  • Kanon (IlmHamroh, 2026-10-28): hujjat.ts, @ApiExcludeEndpoint, test/hujjat.test.ts; Zod sxemalaridan konverter'siz hujjat, @All() va operationId topilmalari; kanon tekshiruvi 120/121. @ApiProperty, CLI plugini (.dto.ts qoidasi), @ApiBearerAuth tajribalari, tuzatishli va tuzatishsiz /docs-json, formatlangan kodning tip/lint/test tekshiruvi — biz ishga tushirdik (2026-11-02, @nestjs/swagger 12.0.2, NestJS 12.2.0).
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS Swagger: @nestjs/swagger bilan OpenAPI hujjati, DTO va Zod sxemalari — IlmHamroh