Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Asosiy uchlik: DocumentBuilder, createDocument, setup
- 3. Tana sxemasi qayerdan keladi? Nest standarti
- 3.1 @ApiProperty
- 3.2 CLI plugini — dekoratorsiz
- 4. Kanon yo'li: Zod sxemasidan, konverter'siz
- 4.1 Standard JSON Schema
- 4.2 Natija
- 5. Tajriba topgan ikki xato
- 5.1 @All() hujjatni egallab oldi
- 5.2 Versiyalar: [0] va ikki nusxa
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): qaysi dekorator?
- 2-mashq (o'rta): hujjatni tozalash
- 3-mashq (qiyin): hujjat linteri
- 9. Loyiha qadami: /docs va /docs-json
- 9.1 Paket
- 9.2 src/hujjat.ts
- 9.3 src/ilova.ts
- 9.4 @ApiExcludeEndpoint() — to'rt joyda
- 9.5 Test
- 9.6 Tekshiruv va commit
- 10. Real ishda
- Xulosa
- Manbalar
NestJS Swagger: @nestjs/swagger bilan OpenAPI hujjati, DTO va Zod sxemalari
Qisqacha:
@nestjs/swaggerNest 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 (@ApiPropertyyoki CLI plugini). NestJS 12 da esa to'g'ridan Zod sxemasidan ham.vazifalar-apida hujjat konverter'siz Zod'dan chiqdi, lekin ikki tuzoq topildi:@All()yo'llari hujjatni buzadi, versiyalar esaoperationIdga[0]qo'shadi.
Bu darsda
DocumentBuilder,createDocumentvasetupbilan/docsva/docs-jsonni ulaysiz.@ApiProperty, CLI plugini,@ApiResponseva@ApiBearerAuthqanday 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-apiga 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
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):
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:
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—@ApiPropertydagi hamma narsa sxemaga o'tdi: tur (string— TypeScript turidan, metama'lumot orqali),example, chegaralar.required— ikkala maydon, chunki ular ixtiyoriy (?) emas.responses—@ApiResponselar: 201 va 409. Nest ularni o'zi bilmaydi — siz yozasiz.securityvasecuritySchemes—@ApiBearerAuth()+addBearerAuth(): "bu yo'lAuthorization: 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— sukutdaKlass_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:
{
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": { "introspectComments": true }
}
]
}
}Dekoratorsiz DTO:
// 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):
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
Taomklassinit-swagger.tsfaylida yozdik — plugin yoqilgan bo'lsa ham natija bo'sh edi. Klassnitaom.dto.tsga ko'chirgach to'ldi. Plugin sukutda faqat.dto.tsva.entity.tsbilan tugaydigan fayllarni o'qiydi (dtoFileNameSuffixsozlamasi).
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 buildpaytida 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):
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 borPOST 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):
/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:
@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:
/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,
/docsochiladi. Faqat hujjat noto'g'ri. Shuning uchun loyiha qadamida hujjatning o'zini tekshiradigan test yoziladi —get,post,putaynan shu yo'lda bo'lishi vaoperationId: "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
/docsni 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:
/vazifalarhali 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
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:
[ '/v1/eksportlar', '/v1/eksportlar/{id}' ]
boshla
holat
3Oxirgi 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
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:
* /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'qYaxshi 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
npm install --save-exact @nestjs/swagger@12.0.2swagger-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):
// 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:
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:
@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):
// 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
npm run check
npm startBiz 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.
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
/docsyoki/apida. Frontend va mobil jamoalar birinchi shu sahifani so'raydi. - Klient generatsiyasi —
/docs-jsondanopenapi-typescript,orvalkabi 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", …):/docsUI,/docs-jsonJSON.- Nest standarti: DTO +
@ApiPropertyyoki CLI plugini (.dto.tsfayllari,nest buildda);@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
operationIdga[0]qo'shadi va eskirgan yo'llarni takrorlaydi —operationIdFactoryva/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()vaoperationIdtopilmalari; kanon tekshiruvi 120/121.@ApiProperty, CLI plugini (.dto.tsqoidasi),@ApiBearerAuthtajribalari, tuzatishli va tuzatishsiz/docs-json, formatlangan kodning tip/lint/test tekshiruvi — biz ishga tushirdik (2026-11-02,@nestjs/swagger12.0.2, NestJS 12.2.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!