Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Standard Schema — qisqa takror
- 3. @Body({ schema }) — NestJS 12
- 3.1 Sxema qayerga yoziladi
- 3.2 O'rnatilgan StandardSchemaValidationPipe
- 3.3 Nega kanon o'rnatilgan pipe'ni ishlatmadi
- 4. nestjs-zod: eski yo'l
- 4.1 createZodDto
- 4.2 NestJS 12 bilan
- 5. Nega kanonda Zod: o'lchov
- 5.1 Tezlik: HTTP
- 5.2 Tezlik: bitta tekshiruv
- 5.3 Kod hajmi va "bitta manba"
- 5.4 Bog'liqliklar
- 5.5 Xulq
- 5.6 Qaror
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): bo'shliqni to'ldiring
- 2-mashq (o'rta): SxemaPipe modeli
- 3-mashq (qiyin): qisman muvaffaqiyat
- 9. Loyiha qadami: sxemali parametrlar
- 9.1 Nima o'zgaradi
- 9.2 vazifalar.controller.ts
- 9.3 Eksport va AppModule
- 9.4 Tekshiruv: ikkita kutilgan xato
- 10. Real ishda
- Xulosa
- Manbalar
Zod va Standard Schema NestJS'da: @Body({ schema }), nestjs-zod va o'lchov
Qisqacha: NestJS 12 da parametr dekoratoriga sxema beriladi:
@Body({ schema: YangiVazifaSxemasi }),@Param("id", { schema: IdSxemasi }). Sxema — Standard Schema obyekti (Zod, Valibot, ArkType); uni pipemetadata.schemadan oladi va~standard.validate()bilan tekshiradi. Natija — sxemaning chiqishi:"7"→7," Non "→"Non". Tayyor pipe —StandardSchemaValidationPipe; eski yo'l —nestjs-zodpaketiningcreateZodDto.vazifalar-api25-qismdagi Zod sxemalarini o'zgarishsiz ishlatadi. Kanon o'lchovida 6 parametrli ro'yxat so'rovida class-validator varianti Zod variantining ~45 % tezligida ishladi.
Bu darsda
@Body/@Query/@Param({ schema })yozuvi qanday ishlashini va pipe sxemani qayerdan olishini tushunasiz.- O'rnatilgan
StandardSchemaValidationPipevanestjs-zodni haqiqiy javoblarda ko'rasiz. - class-validator va Zod'ni to'rt o'lchov bo'yicha solishtirasiz: tezlik, kod hajmi, bog'liqliklar, xulq.
vazifalar-apicontroller'larini sxemali parametrlarga o'tkazasiz va bitta kutilgan test xatosini o'qiysiz.
Oldin bilishingiz kerak: Zod 4 serverda, Backend validatorlari: Joi, class-validator va express-validator, Pipe'lar, Mapped types va nested validatsiya.
O'tgan darsda SxemaPipe ni yozdik: u metadata.schema ni o'qiydi. Bu darsda o'sha schema qayerdan kelishini ko'ramiz va pipe'ni ishga tushiramiz.
1. Nega bu kerak?
Uch dars davomida class-validator'ni o'rgandik — Nest dunyosining standart yo'li. vazifalar-api da esa 25-qismdan beri Zod sxemalari bor: YangiVazifaSxemasi, RoyxatQuerySxemasi, IdSxemasi. Ular sinalgan, testlari bor, frontend ham Zod'da.
Savol: Nest'da Zod'ni qanday ishlatamiz? Hozirgi controller'lar uni handler ichida chaqiradi — tekshir(YangiVazifaSxemasi, req.body, "body"). Bu Express usuli: har handlerda takror, @Req() ga bog'langan, tekshiruv handler'ning birinchi qatorlarida "yashiringan".
NestJS 12 gacha bu savolga jamiyat paketlari javob berardi (nestjs-zod). NestJS 12 da Nest'ning o'zi Standard Schema ni qo'llay boshladi: sxema to'g'ridan-to'g'ri parametr dekoratoriga yoziladi. Ya'ni Zod endi "ruxsat etilgan muqobil" emas, Nest'ning birinchi darajali yo'li.
Sxemalar o'zgarmaydi — o'zgaradigan narsa faqat chaqiriladigan joy:
| Express va hozirgi controller | Shu darsdan keyin | |
|---|---|---|
| Sxema | YangiVazifaSxemasi (25-qism) |
o'sha |
| Kim chaqiradi | handler: tekshir(sxema, req.body, "body") |
global SxemaPipe |
| Sxema qayerda yoziladi | handler ichida | parametr dekoratorida: @Body({ schema }) |
| Handler nima oladi | req |
tayyor, aylantirilgan qiymat |
Yutuq — handler'lar qisqaradi va @Req() ga bog'liqlik kamayadi. Narx — tekshiruv tartibi o'zgaradi; buning bitta kutilgan oqibatini «Loyiha qadami» da ko'ramiz. Ya'ni bu dars yangi validatsiya kutubxonasini o'rgatmaydi. U bor sxemalarni Nest'ning so'rov yo'liga to'g'ri joyga ko'chiradi.
O'xshatish: elektr vilkasi. Har davlatning o'z rozetkasi bo'lsa, har asbobga adapter kerak. Standard Schema — umumiy rozetka standarti: Zod, Valibot, ArkType — har biri o'z "asbobi", lekin vilkasi bir xil (~standard). Nest rozetkani bir marta o'rnatdi — qaysi kutubxonani ulasangiz ham ishlaydi.
2. Standard Schema — qisqa takror
Backend validatorlari darsida ko'rganimiz: Standard Schema'ni qo'llaydigan har sxemada ~standard xususiyati bor. Unda vendor (kutubxona nomi) va validate(qiymat) metodi. validate natijasi — yo { value }, yo { issues }.
Ikkita turli kutubxona — bitta interfeys:
import { z } from "zod";
import * as v from "valibot";
const zodSxema = z.object({ matn: z.string().min(1) });
const valibotSxema = v.object({
matn: v.pipe(v.string(), v.minLength(1)),
});
for (const s of [zodSxema, valibotSxema]) {
const yomon = await s["~standard"].validate({ matn: "" });
const yaxshi = await s["~standard"].validate({ matn: "Non" });
console.log(
s["~standard"].vendor,
yomon.issues?.length,
JSON.stringify(yaxshi.issues === undefined ? yaxshi.value : null),
);
}Konsolda:
zod 1 {"matn":"Non"}
valibot 1 {"matn":"Non"}Kod zodSxema va valibotSxema farqini bilmaydi — faqat ~standard ni chaqiradi. SxemaPipe aynan shunday yozilgan: unda zod importi yo'q.
3. @Body({ schema }) — NestJS 12
3.1 Sxema qayerga yoziladi
@Post(':id')
yarat(
@Param('id', { schema: Id }) id: number,
@Body({ schema: YangiVazifa }) body: YangiVazifa,
) { … }Yangi shakl: dekoratorga obyekt beriladi — { schema }, kerak bo'lsa { schema, pipes: [...] }. Nomli parametr (@Param("id", ...)) uchun obyekt ikkinchi argument.
Dekorator o'zi hech narsani tekshirmaydi. U sxemani marshrut metama'lumotiga — parametr yozuvining yoniga — qo'yib qo'yadi. So'rov kelganda Nest har parametr uchun pipe'larni chaqiradi va ArgumentMetadata.schema ga o'sha sxemani beradi:
sequenceDiagram
participant D as "@Body({ schema })"
participant N as Nest router
participant P as Pipe
participant H as Handler
D->>N: ishga tushishda: sxema metama'lumotga
N->>P: transform(tana, { type: "body", schema })
P->>P: schema["~standard"].validate(tana)
P-->>N: value (yoki xato)
N->>H: yarat(id, value)Diagrammaning birinchi qatoriga qarang: sxema ilova ishga tushganda bir marta yoziladi; har so'rovda faqat pipe ishlaydi. Pipe bo'lmasa — sxema bor, lekin hech kim uni tekshirmaydi.
3.2 O'rnatilgan StandardSchemaValidationPipe
NestJS 12 tayyor pipe beradi. Demo loyihada Zod sxemalari bilan sinadik (biz ishga tushirdik: NestJS 12.2.0, zod 4.6.7, Node 24.21.0):
const YangiVazifa = z.strictObject({
matn: z.string().trim().min(1).max(100),
bajarildi: z.boolean().optional(),
});
type YangiVazifa = z.infer<typeof YangiVazifa>;
const Id = z.string().regex(/^[1-9]\d*$/).transform(Number);
@Controller()
class C {
@Post('nest/:id')
nest(
@Param('id', { schema: Id }) id: number,
@Body({ schema: YangiVazifa }) body: YangiVazifa,
) {
return { id, turi: typeof id, body };
}
}
app.useGlobalPipes(new StandardSchemaValidationPipe());POST /nest/7 {"matn":" Non olish "}
→ 201 {"id":7,"turi":"number","body":{"matn":"Non olish"}}
POST /nest/7 {"matn":"","admin":true}
→ 400 {"message":["matn: Too small: expected string to have >=1 characters","Unrecognized key: \"admin\""],"error":"Bad Request","statusCode":400}
POST /nest/07 {"matn":"x"}
→ 400 {"message":["Invalid string: must match pattern /^[1-9]\\d*$/"],"error":"Bad Request","statusCode":400}Uch narsaga qarang:
- Aylantirish sxemaning o'zida.
id—7soni (transform(Number)),matn— kesilgan. Handler sxemaning chiqishini oladi. class-validator'dagi "tekshirish — bir joyda, aylantirish — boshqa joyda" muammosi yo'q. "07"rad etildi — regex tekshiruvi aylantirishdan oldin. O'tgan ikki darsdagi"01"→ 200 xatosi bu yerda yuz bermaydi.- Ortiqcha maydon —
z.strictObjecttufayli "Unrecognized key".whitelistsozlamasi kerak emas — qoida sxemaning o'zida.
Xabarlar inglizcha — Zod'ning sukut tili. vazifalar-api da z.config(z.locales.uz()) yoqilgan, shuning uchun u yerda o'zbekcha chiqadi.
3.3 Nega kanon o'rnatilgan pipe'ni ishlatmadi
StandardSchemaValidationPipe ning sozlamalari: errorHttpStatusCode (sukut 400) va exceptionFactory(issues). vazifalar-api ga esa tana xatosi uchun 422, query va yo'l uchun 400 kerak. exceptionFactory faqat issues ni oladi — xato tanadanmi yoki query'danmi, bilmaydi. ValidationPipe sozlamalari darsidagi o'sha muammo. Shuning uchun kanon o'z SxemaPipe ini yozdi: 30 qator va metadata.type bo'yicha bitta if.
Tekshirib ko'ring: Global pipe sifatida
StandardSchemaValidationPipeulangan. Controller'da@Body() body: YangiVazifayozilgan —schemayo'q. Mijoz{"matn": 5}yuborsa nima bo'ladi?
Javob
O'tadi. Pipe metadata.schema ni qidiradi — yo'q, demak tekshiradigan narsa yo'q, qiymat o'zgarishsiz uzatiladi. YangiVazifa — TypeScript turi, u ish vaqtida yo'q (DTO va class-validator darsidagi interfeys tuzog'ining o'zi). Sxemali yo'lda har parametrga { schema } yozish shart — buni review va testlar nazorat qiladi.
4. nestjs-zod: eski yo'l
4.1 createZodDto
NestJS 12 gacha Zod bilan ishlashning eng mashhur yo'li — nestjs-zod paketi. U Zod sxemasidan DTO klassi yasaydi: class YangiVazifaDto extends createZodDto(YangiVazifa) {}. Klass bor — demak @Body() body: YangiVazifaDto yoziladi va paketning ZodValidationPipe i metatype dan sxemani topadi. Afzalligi — klass orqali Swagger hujjati ham yasaladi.
4.2 NestJS 12 bilan
Demo loyihaga o'rnatishga urindik (2026-11-02, nestjs-zod 5.5.0 — npm'dagi eng yangi):
npm install --save-exact nestjs-zod@5.5.0npm error Found: @nestjs/common@12.2.0
npm error node_modules/@nestjs/common
npm error @nestjs/common@"12.2.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer @nestjs/common@"^10.0.0 || ^11.0.0" from nestjs-zod@5.5.0
npm error node_modules/nestjs-zod
npm error nestjs-zod@"5.5.0" from the root projectTarjimasi: "Bog'liqlikni hal qilib bo'lmadi: nestjs-zod@5.5.0 @nestjs/common ning 10 yoki 11-versiyasini talab qiladi (peer), loyihada esa 12.2.0". Semver darsidagi ^11.0.0 — "11.x.x, lekin 12 emas". Paket hali NestJS 12 ni rasman qo'llamaydi.
Buni o'rnatishdan oldin ham bilish mumkin edi. npm view buyrug'i paketning peer bog'liqliklarini ko'rsatadi:
npm view nestjs-zod@5.5.0 peerDependenciesBiz 2026-11-02 da tekshirdik — javobda to'rt qator: @nestjs/common uchun ^10.0.0 || ^11.0.0, @nestjs/swagger uchun ^7.4.2 || ^8.0.0 || ^11.0.0, rxjs uchun ^7.0.0, zod uchun ^3.25.0 || ^4.0.0. Ikkinchi qatorga ham qarang: Swagger moduli ham faqat 11-versiyagacha. Bizning loyihada @nestjs/swagger 12.0.2 — demak nestjs-zod ning Swagger qismi bilan ham xuddi shu ziddiyat bo'ladi. O'sha kuni npm'dagi eng yangi versiya ham 5.5.0 edi: NestJS 12 ni qo'llaydigan reliz hali chiqmagan.
Tekshirib ko'ring: Paketning peer'i
"@nestjs/common": "^11.0.0". Loyihangizda@nestjs/common11.1.6.npm installxato beradimi? 12.0.0 bo'lsa-chi?
Javob
11.1.6 — yo'q: ^11.0.0 "11.x.x ning istalgani" degani, 11.1.6 shunga kiradi. 12.0.0 — ha, ERESOLVE: katta raqam o'zgargan, ^ uni qamramaydi (Semver). Katta versiya — "API buzilishi mumkin" degan belgi, shuning uchun paket muallifi 12 ni sinamaguncha qo'shmaydi.
--legacy-peer-deps bayrog'i bilan o'rnatdik (npm peer tekshiruvini o'tkazib yuboradi) va sinadik:
class YangiVazifaDto extends createZodDto(YangiVazifa) {}
@Post('nestjs-zod')
nz(@Body() body: YangiVazifaDto) {
return { body, klass: body.constructor.name };
}
// main.ts: app.useGlobalPipes(new ZodValidationPipe());POST /nestjs-zod {"matn":" Non "}
→ 201 {"body":{"matn":"Non"},"klass":"Object"}
POST /nestjs-zod {"matn":5}
→ 400 {"statusCode":400,"message":"Validation failed","errors":[{"expected":"string","code":"invalid_type","path":["matn"],"message":"Invalid input: expected string, received number"}]}Ishladi: kesish, tur xatosi. Xato formati boshqacha — errors massivida Zod'ning xom issues lari. klass — Object: DTO klassi faqat sxemani tashuvchi, handler esa oddiy obyekt oladi.
Xulosa: bizning bitta sinovimizda ishladi, lekin rasmiy qo'llanmagan versiyani prod'ga qo'yish — xavf. NestJS 12 da o'rnatilgan { schema } yo'li bu paketning asosiy vazifasini qamraydi. nestjs-zod hali kerak bo'ladigan joy — Swagger: lekin vazifalar-api Swagger'ni Zod sxemalaridan to'g'ridan-to'g'ri yasaydi (Swagger).
Diqqat:
--legacy-peer-deps— muammoni yashiradi, hal qilmaydi. Unipackage.jsonskriptlariga yoki.npmrcga doimiy yozib qo'ysangiz, keyingi barcha peer ziddiyatlari ham jim o'tadi.
5. Nega kanonda Zod: o'lchov
Kanon ikkala variantni — Zod va class-validator — xuddi shu endpointlar bilan to'liq yozib, o'lchagan (kanon o'lchovi, IlmHamroh, 2026-10). Ikkalasi ham 25–27 testlarining 52 tasidan o'tgan. Farqlar to'rt o'qda.
5.1 Tezlik: HTTP
Har endpointga yuk berildi (autocannon, 10 ulanish, 5 aylana × 3 soniya, variantlar navbat bilan; mediana). Mashinada bir vaqtda boshqa ishlar ham bo'lgan — mutlaq req/s emas, nisbat muhim. Zod varianti = 100 %:
- Zod (asos)100 %
- GET ro'yxat (6 parametr)44,8 %
- 422 xato yo'li75,2 %
- PATCH (yozuv)78 %
- GET /:id107,2 %
Manba: kanon o'lchovi (IlmHamroh, 2026-10), autocannon mediana, bitta mashina, navbat bilan; NestJS 12.2.0, Node 24.21.0
- GET ro'yxat — eng katta farq: 2 544 va 1 140 req/s, ya'ni Zod ~2.2 marta tez. Bu so'rovda 6 ta query parametri tekshiriladi va aylantiriladi — validatsiya ishi ko'p.
- PATCH va 422 yo'li — class-validator 75–78 %.
- GET /:id — farq yo'q (107 % — o'lchov shovqini ichida). Bitta
idtekshiruvi so'rov vaqtida sezilmaydi.
Ya'ni farq validatsiya ishi ko'p bo'lgan joyda ko'rinadi. Odatiy CRUD'da so'rov vaqtining asosiy qismi baza va tarmoqqa ketadi.
5.2 Tezlik: bitta tekshiruv
Xuddi shu yaroqli tanani 200 000 marta tekshirish (kurs tajribasi, isitishdan keyin; mln/s — soniyada million tekshiruv):
- zod safeParse5,43 mln/s
- zod ~standard.validate3,13 mln/s
- plainToInstance + validateSync0,3 mln/s
- Nest ValidationPipe.transform0,19 mln/s
Manba: kanon tajribasi (IlmHamroh, 2026-10-27), Node 24.21.0, i5-12500H, 200 000 aylana
Zod safeParse — 0.18 µs, Nest ValidationPipe — 5.22 µs: ~27 marta. Standard Schema yo'li (~standard.validate) to'g'ridan safeParse dan sekinroq (Promise qaytaradi), lekin baribir class-validator'dan ~10 marta tez. Bu raqamlar Backend validatorlari darsidagi o'lchov (zod 6.63, class-validator 0.30 mln/s) bilan mos.
Yana bir bor: 5 µs — HTTP so'rovida kichik vaqt. Shuning uchun HTTP o'lchovidagi farq (2.2×) mikro-o'lchovdagidan (27×) ancha kichik.
5.3 Kod hajmi va "bitta manba"
| Zod | class-validator | |
|---|---|---|
| Ro'yxat query'si | 45 qator sxema | 101 qator DTO + aylantirish funksiyasi |
| TypeScript turi | z.infer — sxemadan |
DTO klassi = tur, aylantirilgan tur alohida |
| JSON Schema | z.toJSONSchema — sxemadan |
alohida vosita kerak |
| Obyekt qoidalari | .refine() sxemada |
controller'da |
| Frontend bilan | o'sha sxema | alohida |
class-validator'da bitta qoida uch joyda yashaydi: dekorator (tekshiruv), @Transform yoki aylantirish funksiyasi (o'zgartirish), TypeScript turi. Zod'da — bitta zanjir. vazifalar-api da bu ayniqsa muhim: frontend (15–22-qismlar) va backend bir xil sxema tilida gaplashadi.
5.4 Bog'liqliklar
| Paket | Oxirgi versiya (npm, 2026-10) |
|---|---|
zod |
4.6.7 — loyihada 25-qismdan bor |
class-validator |
0.15.1 — 2026-02-26 |
class-transformer |
0.5.1 — 2021-11-22 |
Zod qolsa — prod bog'liqlik qo'shilmaydi. class-validator yo'li ikki yangi paket qo'shadi, ulardan biri besh yildan beri yangilanmagan. Backend validatorlari darsida o'lchangan hajm ham shu yerga: class-validator + class-transformer + reflect-metadata — 16.7 MB.
5.5 Xulq
33 ta noto'g'ri so'rovdan 22 tasida javoblar aynan bir xil, 11 tasida farq (Mapped types va nested validatsiya darsida ko'rdik). Ikkita haqiqiy xulq farqi (/01 → 200, /1e3 → 404) — class-validator varianti tomonida.
5.6 Qaror
Kanon Zod'ni qoldirdi: tezroq, bitta manba, yangi bog'liqliksiz va xulqi 25-qism shartnomasiga aniqroq mos. Muqobil ham tayyor: class-validator varianti to'liq yozilgan va loyihani unga o'tkazish — 519 qatorlik o'zgarish.
Qachon class-validator to'g'ri tanlov:
- jamoa va mavjud kod class-validator'da — ikki xil validatorni aralashtirmang;
- Nest CLI Swagger plagini DTO klasslaridan hujjatni avtomatik yasashi kerak;
- frontend Zod ishlatmaydi va "bitta manba" afzalligi yo'q.
Ya'ni bu "class-validator yomon" degani emas — o'lchab tanlangan muqobil.
6. Hujumchi nigohi
z.objectvaz.strictObject.z.objectnoma'lum kalitlarni jim olib tashlaydi —whitelistkabi.z.strictObject— xato beradi,forbidNonWhitelistedkabi. Kanon sxemalarining hammasistrictObject.z.coerce— implicit conversion'ning Zod'dagi egizagi.z.coerce.boolean()ichidaBoolean(qiymat)—"false"→true. Kanonz.coerceishlatmaydi — query mantiqiy qiymatlari aniq"true"/"false"bilan (blok pastda).- Sxemasiz parametr.
{ schema }yozish unutilsa — parametr tekshiruvsiz o'tadi («Nega kanon o'rnatilgan pipe'ni ishlatmadi» bo'limidagi savol).@Req()orqalireq.bodyni to'g'ridan o'qish ham shunday. Himoya — har yozuvchi endpoint uchun "noto'g'ri tana → 422" testi. - Peer ziddiyatini yashirish.
--legacy-peer-depsbilan o'rnatilgan paket keyingi yangilanishda jim buzilishi mumkin. Himoya — rasmiy qo'llanadigan versiya yoki o'rnatilgan yo'l.
z.coerce tuzog'ini brauzerda tekshiring:
import { z } from "zod";
console.log(z.coerce.boolean().parse("false"));
const Mantiqiy = z.enum(["true", "false"])
.transform((x) => x === "true");
console.log(Mantiqiy.parse("false"));
console.log(Mantiqiy.safeParse("ha").success);Konsolda:
true
false
falseBirinchi qator — z.coerce ning "false" → true xatosi. Keyingi ikkitasi — aniq sxema: "false" → false, "ha" — rad.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Sxema bor, lekin tekshirilmaydi | global pipe ulanmagan | StandardSchemaValidationPipe yoki o'z pipe |
| Ortiqcha maydon jim yo'qoladi | z.object |
z.strictObject |
"false" → true |
z.coerce.boolean() |
z.enum(["true","false"]).transform(...) |
npm error ERESOLVE ... peer @nestjs/common |
paket Nest 12 ni qo'llamaydi | o'rnatilgan { schema } yo'li |
| 422 o'rniga 400 | StandardSchemaValidationPipe bitta status |
o'z pipe (metadata.type) |
Handler'da id satr |
sxemada .transform(Number) yo'q |
sxema chiqishi = handler kirishi |
8. Mashqlar
Mashqlar kurs/mashqlar/30/19-standard-schema/ da.
1-mashq (oson): bo'shliqni to'ldiring
- Tanaga sxema berish —
@Body({ : YangiVazifaSxemasi }) - Pipe sxemani oladigan joy —
metadata. - Standard Schema metodi —
sxema["~standard"].(qiymat) - Noma'lum kalitda xato beradigan Zod obyekti —
z. - NestJS 12 ning tayyor pipe'i —
Yechim
- schema —
ParameterDecoratorOptionsning maydoni; nomli parametrda ikkinchi argument:@Param("id", { schema }). - schema —
ArgumentMetadata.schema. - validate —
{ value }yoki{ issues }; Promise qaytarishi mumkin. - strictObject —
z.objectjim tashlaydi. - StandardSchemaValidationPipe — sukut 400,
exceptionFactory(issues).
2-mashq (o'rta): SxemaPipe modeli
SxemaPipe ning mantiqini kutubxonasiz takrorlang. Kichik Standard Schema obyektini qo'lda yasang (~standard.validate — matn satr va bo'sh emasmi). pipe(qiymat, metadata) funksiyasi: metadata.schema yo'q bo'lsa — qiymatni o'zgarishsiz qaytarsin; xato bo'lsa { status, xatolar } tashlasin: type === "body" — 422, aks holda 400; joy — body.matn ko'rinishida. Ishora: map va join (Massiv bo'ylab yurish).
Yechim
const MatnSxemasi = {
"~standard": {
version: 1,
vendor: "qolda",
validate(v) {
if (typeof v?.matn !== "string" || v.matn.trim() === "") {
const xato = { path: ["matn"], message: "matn bo'sh" };
return { issues: [xato] };
}
return { value: { matn: v.matn.trim() } };
},
},
};
const JOY = { body: "body", query: "query", param: "params" };
async function pipe(qiymat, metadata) {
const joy = JOY[metadata.type];
if (metadata.schema === undefined || joy === undefined) {
return qiymat;
}
const n = await metadata.schema["~standard"].validate(qiymat);
if (n.issues === undefined) return n.value;
const xatolar = n.issues.map((i) => ({
joy: [joy, ...(i.path ?? [])].join("."),
xabar: i.message,
}));
throw { status: metadata.type === "body" ? 422 : 400, xatolar };
}
const body = { type: "body", schema: MatnSxemasi };
console.log(await pipe({ matn: " Non " }, body));
console.log(await pipe({ x: 1 }, { type: "body" }));
try {
await pipe({ matn: " " }, body);
} catch (x) {
console.log(x.status, JSON.stringify(x.xatolar));
}
try {
await pipe({}, { type: "query", schema: MatnSxemasi });
} catch (x) {
console.log(x.status, JSON.stringify(x.xatolar));
}Konsolda:
{ matn: 'Non' }
{ x: 1 }
422 [{"joy":"body.matn","xabar":"matn bo'sh"}]
400 [{"joy":"query.matn","xabar":"matn bo'sh"}]Ikkinchi qator — sxemasiz parametrning xavfi: { x: 1 } hech narsasiz o'tdi. Haqiqiy SxemaPipe da ham shunday — shuning uchun { schema } ni unutmaslik testlar bilan nazorat qilinadi.
3-mashq (qiyin): qisman muvaffaqiyat
POST /v1/vazifalar/toplam da har element alohida tekshiriladi va o'z statusini oladi. toplam(elementlar, sxema) funksiyasini yozing: har element uchun sxema bilan tekshiring; yaroqli — { indeks, holat: 201, vazifa: {id, ...} } (id — 1 dan ketma-ket), yaroqsiz — { indeks, holat: 422, xato }. Umumiy status: hammasi 201 bo'lsa — 201, aks holda 207. Oldingi mashqdagi MatnSxemasi ni ishlating (bu yerda sinxron chaqirsa bo'ladi — u Promise qaytarmaydi).
Yechim
const MatnSxemasi = {
"~standard": {
validate(v) {
if (typeof v?.matn !== "string" || v.matn.trim() === "") {
return { issues: [{ message: "matn bo'sh" }] };
}
return { value: { matn: v.matn.trim() } };
},
},
};
function toplam(elementlar, sxema) {
let id = 0;
const natijalar = elementlar.map((el, indeks) => {
const n = sxema["~standard"].validate(el);
if (n.issues) {
const xabar = n.issues.map((i) => i.message).join("; ");
return { indeks, holat: 422, xato: xabar };
}
return { indeks, holat: 201, vazifa: { id: ++id, ...n.value } };
});
const hammasi = natijalar.every((n) => n.holat === 201);
return { status: hammasi ? 201 : 207, natijalar };
}
const javob = toplam([{ matn: "A" }, { matn: "" }, 5], MatnSxemasi);
console.log(javob.status);
for (const n of javob.natijalar) console.log(JSON.stringify(n));
console.log(toplam([{ matn: "B" }], MatnSxemasi).status);Konsolda:
207
{"indeks":0,"holat":201,"vazifa":{"id":1,"matn":"A"}}
{"indeks":1,"holat":422,"xato":"matn bo'sh"}
{"indeks":2,"holat":422,"xato":"matn bo'sh"}
201Kanon controller'idagi toplam metodi xuddi shu tuzilmada — faqat YangiVazifaSxemasi.safeParse(el) bilan va yaroqlilarni omborga bitta chaqiruvda yozadi. BulkSxemasi esa faqat tashqi qobiqni (1..100 elementli massiv) pipe orqali tekshiradi.
9. Loyiha qadami: sxemali parametrlar
9.1 Nima o'zgaradi
- Controller'lar:
tekshir(...)chaqiruvlari o'rniga@Body/@Query/@Param({ schema }). Sxemalar (src/vazifalar/sxema.ts) o'zgarmaydi. AppModule:SxemaPipeglobal pipe sifatida ulanadi.- Handler'lardan
jsonTanaKerak(req)chaqiruvi ham olib tashlanadi (sababi va oqibati — pastda).
9.2 vazifalar.controller.ts
Kanon faylida qatorlar uzun edi — telefon uchun bo'ldik va izohlarni qisqartirdik (kod ma'nosi aynan). Bu — faylning shu darsdagi holati; bo'lingan nusxani tsc --noEmit va ESLint bilan tekshirdik.
// vazifalar.controller.ts — /v1/vazifalar: HTTP ↔ xizmat (25-qism
// marshrut.ts dan). Kirish: Nest 12 `@Body/@Query/@Param({ schema })`
// — 25-qism Zod sxemalari (sxema.ts) o'zgarmadi, tekshiruvni global
// SxemaPipe qiladi. @Res({ passthrough: true }) — faqat sarlavhalar
// uchun
import {
All, Body, Controller, Delete, Get, HttpCode, Inject, Param, Patch,
Post, Put, Query, Req, Res,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { MetodYoqXatosi } from "../umumiy/xatolar.ts";
import type { MaydonXatosi } from "../umumiy/xatolar.ts";
import { kursorYoz } from "../umumiy/kursor.ts";
import {
ifMatch, keshYoq, vazifaSarlavhalari,
} from "../umumiy/shartli.ts";
import { asosYol } from "../umumiy/yol.ts";
import { ILOVA_SOZLAMALARI } from "../tokenlar.ts";
import type { IlovaSozlamalari } from "../tokenlar.ts";
import {
BulkSxemasi, IdSxemasi, OzgarishSxemasi, RoyxatQuerySxemasi,
ToliqVazifaSxemasi, ToplamSxemasi, YangiVazifaSxemasi,
} from "./sxema.ts";
import type { RoyxatQuery } from "./sxema.ts";
import type {
ToliqVazifa, Vazifa, VazifaOzgarishi, YangiVazifa,
} from "./vazifa.ts";
import type { AlmashtirishVazifasi } from "./ombor.ts";
import { proyeksiya, royxatSorovi } from "./sorov.ts";
import { VazifalarService } from "./vazifalar.service.ts";
/** Keyingi sahifa havolasi: query nusxasi, offset/cursor yangi */
function keyingiHavola(
req: Request,
ozgartir: Record<string, string>,
): string {
const p = new URLSearchParams(req.originalUrl.split("?")[1] ?? "");
p.delete("offset");
p.delete("cursor");
for (const [k, v] of Object.entries(ozgartir)) p.set(k, v);
return `${asosYol(req, "vazifalar")}?${p.toString()}`;
}
type Natija = {
indeks: number;
holat: number;
vazifa?: Vazifa;
xato?: MaydonXatosi;
};
type Id = { id: number };
// Versiya — ilova.ts dagi enableVersioning (URI): /v1/vazifalar
// va eskirgan /vazifalar
@Controller("vazifalar")
export class VazifalarController {
constructor(
private readonly xizmat: VazifalarService,
@Inject(ILOVA_SOZLAMALARI)
private readonly sozlamalar: IlovaSozlamalari,
) {}
@Get()
async royxat(
@Query({ schema: RoyxatQuerySxemasi }) query: RoyxatQuery,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<object> {
const { sorov, maydonlar, imzo } =
royxatSorovi(query, this.sozlamalar.tz);
const natija = await this.xizmat.royxat(sorov);
const sahifa: Record<string, unknown> = { limit: sorov.limit };
let keyingi: string | undefined;
if (sorov.sahifa.tur === "offset") {
sahifa.offset = sorov.sahifa.offset;
if (natija.borKeyingi) {
const offset = String(sorov.sahifa.offset + sorov.limit);
keyingi = keyingiHavola(req, { offset });
}
} else {
const kursor =
natija.keyingi && kursorYoz(natija.keyingi, imzo);
sahifa.keyingi = kursor ?? null;
if (kursor !== undefined) {
keyingi = keyingiHavola(req, { cursor: kursor });
}
}
if (natija.jami !== undefined) sahifa.jami = natija.jami;
// Link sarlavhasi (RFC 8288) — mijoz URL'ni o'zi yig'masin
if (keyingi !== undefined) res.links({ next: keyingi });
// ro'yxat ETag'i — Express'niki (tana xeshi, W/"…"): Nest javobni
// res.json() bilan yuboradi → If-None-Match mos kelsa 304
keshYoq(res);
return {
versiya: natija.versiya,
vazifalar: natija.vazifalar.map(
(v) => proyeksiya(v, maydonlar),
),
sahifa,
};
}
@Post()
async yarat(
@Body({ schema: YangiVazifaSxemasi }) body: YangiVazifa,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<Vazifa> {
const v = await this.xizmat.yarat(body);
vazifaSarlavhalari(res, v);
res.location(`${asosYol(req, "vazifalar")}/${String(v.id)}`);
return v; // POST — sukut 201
}
/** PUT to'plamga — butun ro'yxatni almashtirish; atomik */
@Put()
async almashtirHammasi(
@Body({ schema: ToplamSxemasi })
body: { vazifalar: AlmashtirishVazifasi[] },
@Res({ passthrough: true }) res: Response,
): Promise<object> {
const { vazifalar, versiya } =
await this.xizmat.almashtirHammasi(body.vazifalar);
keshYoq(res);
return { versiya, vazifalar };
}
// Express metod mos kelmasa 404 beradi — 405 + Allow ni o'zimiz
// qo'yamiz (Allow ro'yxatini marshrutlar.test.ts tekshiradi)
@All()
toplamMetodYoq(@Req() req: Request): never {
throw new MetodYoqXatosi(req.method, "GET, HEAD, POST, PUT");
}
/** Bulk yaratish — qisman muvaffaqiyat: 207 Multi-Status
* (/:id dan OLDIN — aks holda "toplam" id deb olinadi) */
@Post("toplam")
async toplam(
@Body({ schema: BulkSxemasi }) body: { vazifalar: unknown[] },
@Res({ passthrough: true }) res: Response,
): Promise<{ natijalar: Natija[] }> {
// elementlar alohida tekshiriladi (qisman muvaffaqiyat) — pipe
// emas, sxema bevosita
const tekshirilgan = body.vazifalar.map(
(el) => YangiVazifaSxemasi.safeParse(el),
);
const yaroqli = tekshirilgan.flatMap(
(t) => (t.success ? [t.data] : []),
);
const yaratilgan = await this.xizmat.toplamYarat(yaroqli);
let k = 0;
const natijalar = tekshirilgan.map((t, indeks): Natija => {
if (!t.success) {
const xabar = t.error.issues.map((i) => i.message).join("; ");
const joy = `body.vazifalar[${String(indeks)}]`;
return { indeks, holat: 422, xato: { joy, xabar } };
}
const vazifa = yaratilgan[k++];
return vazifa === undefined
? { indeks, holat: 500 }
: { indeks, holat: 201, vazifa };
});
res.status(natijalar.every((n) => n.holat === 201) ? 201 : 207);
return { natijalar };
}
@All("toplam")
toplamPostYoq(@Req() req: Request): never {
throw new MetodYoqXatosi(req.method, "POST");
}
@Get(":id")
async ol(
@Param({ schema: IdSxemasi }) { id }: Id,
@Res({ passthrough: true }) res: Response,
): Promise<Vazifa> {
const v = await this.xizmat.ol(id);
vazifaSarlavhalari(res, v);
keshYoq(res);
return v;
}
/** PUT — to'liq almashtirish (idempotent: 2 marta = 1 marta) */
@Put(":id")
async almashtir(
@Param({ schema: IdSxemasi }) { id }: Id,
@Body({ schema: ToliqVazifaSxemasi }) body: ToliqVazifa,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<Vazifa> {
const v = await this.xizmat.almashtir(id, body, ifMatch(req));
vazifaSarlavhalari(res, v);
return v;
}
/** PATCH — qisman (JSON Merge Patch). If-Match: "3" — faqat
* 3-versiya bo'lsa yoz */
@Patch(":id")
async yangila(
@Param({ schema: IdSxemasi }) { id }: Id,
@Body({ schema: OzgarishSxemasi }) body: VazifaOzgarishi,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<Vazifa> {
const v = await this.xizmat.yangila(id, body, ifMatch(req));
vazifaSarlavhalari(res, v);
return v;
}
@Delete(":id")
@HttpCode(204)
async ochir(
@Param({ schema: IdSxemasi }) { id }: Id,
@Req() req: Request,
): Promise<void> {
await this.xizmat.ochir(id, ifMatch(req));
}
@All(":id")
birMetodYoq(@Req() req: Request): never {
throw new MetodYoqXatosi(
req.method, "GET, HEAD, PUT, PATCH, DELETE",
);
}
}Controller'lar darsidagi holat bilan farqlar:
- Kirish — parametrda.
tekshir(IdSxemasi, req.params, "params")o'rniga@Param({ schema: IdSxemasi }) { id }: Id. Destrukturlash ishlaydi, chunki pipe sxemaning chiqishini beradi:{ id: 7 }(son). @Req()kamaydi.ol,almashtirHammasi,toplamendireqsiz. Qolgan joylarda uasosYolvaifMatchuchun — keyingi darslarda o'z dekoratorlarimiz bilan almashadi.toplam— tashqi qobiq (BulkSxemasi) pipe'da, elementlar esa handler'dasafeParsebilan: qisman muvaffaqiyat uchun («Qisman muvaffaqiyat» mashqi).tekshirvajsonTanaKerakimportlari — fayldan yo'qoldi.
9.3 Eksport va AppModule
eksport.controller.ts da faqat boshla metodi o'zgaradi (importga Body qo'shiladi, tekshir importi o'chadi, EksportSorovi turi sxema.ts dan):
@Post()
@HttpCode(202)
boshla(
@Body({ schema: EksportSxemasi }) body: EksportSorovi,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Record<string, unknown> {
const ish = this.eksport.boshla(body);
const asos = asosYol(req, "eksportlar");
res.location(`${asos}/${ish.id}`).set("Retry-After", "1");
return this.eksport.korinish(ish, asos);
}app.module.ts da forRoot qaytaradigan dinamik modulga providers qo'shiladi:
import { APP_PIPE } from "@nestjs/core";
import { SxemaPipe } from "./umumiy/sxema.pipe.ts";
// forRoot(...) ichida, return { module, imports, … } ga:
providers: [
{ provide: APP_PIPE, useClass: SxemaPipe },
],APP_PIPE — "bu provider global pipe" degan maxsus token. app.useGlobalPipes(new SxemaPipe()) ham ishlardi; nega APP_PIPE afzal — Global enhancer'lar va bajarilish tartibi darsida.
9.4 Tekshiruv: ikkita kutilgan xato
npm run tip va npm run lint toza o'tishi kerak. Testlar esa — yo'q. Biz shu holatni kanon nusxasida yurgizdik (biz ishga tushirdik, Vitest 5.0.5; Postgres'siz — bazaga ulanadigan testlar SKIP):
❯ test/xato.test.ts (3 tests | 1 failed) 1871ms
× Problem Details: 404 marshrut (about:blank), 405 Allow bilan, 415 215ms
❯ test/api.test.ts (5 tests | 1 failed) 2222ms
× xatolar: 400 JSON, 415, 422, 405 + Allow, 404 125ms
FAIL test/api.test.ts > xatolar: 400 JSON, 415, 422, 405 + Allow, 404
AssertionError: Expected values to be strictly equal:
422 !== 415
❯ test/api.test.ts:39:10
37| assert.deepEqual(await xom.json(), { type: "about:blank", title: "Ba…
38| const matnli = await fetch(`${asos}/v1/vazifalar`, { method: "POST",…
39| assert.equal(matnli.status, 415);
| ^
Test Files 2 failed | 16 passed | 5 skipped (23)
Tests 2 failed | 73 passed | 6 skipped (81)(Chiqishni qisqartirdik: ikkinchi xatoning tafsiloti birinchisi bilan bir xil.) Tarjimasi: "kutilgan 415, keldi 422". Test Content-Type: text/plain bilan POST yuboradi va 415 Unsupported Media Type kutadi. Biz esa 422 qaytardik.
Nega? Avval jsonTanaKerak(req) handler'ning birinchi qatori edi va tekshiruvdan oldin ishlardi. Endi tekshiruv — pipe'da, pipe esa handler'dan oldin. text/plain tanani express.json() o'qimaydi, req.body — undefined, sxema uni rad etadi — 422. Handler'gacha navbat yetmaydi.
Request lifecycle dagi tartib: middleware → guard → interceptor → pipe → handler. 415 tekshiruvi pipe'dan oldin bo'lishi kerak — demak guard'ga. Buni keyingi darsning «Loyiha qadami» da qilamiz.
Faqat shu ikki test yiqilsa — hammasi to'g'ri. Boshqa xato chiqsa (masalan, ParametrXatosi o'rniga 500), uni hozir tuzating. Hali commit qilmang.
10. Real ishda
- NestJS 12 bilan yangi loyihalarda Zod (yoki Valibot) —
{ schema }orqali tabiiy tanlov; class-validator — mavjud kod bazalarida va Swagger plagini kerak bo'lganda. - Paket tanlashda
npm view <paket> peerDependencies— "bu Nest versiyamni qo'llaydimi" degan savolning eng tez javobi. - Intervyu: "Standard Schema nima?", "class-validator va Zod farqi?", "validatsiya qaysi qatlamda — pipe, guard yoki xizmatda?".
Xulosa
- NestJS 12:
@Body/@Query/@Param({ schema })— sxemani parametr metama'lumotiga yozadi; pipemetadata.schemadan oladi. - Standard Schema —
~standard.validate(): Zod, Valibot, ArkType bir xil ulanadi; handler sxema chiqishini oladi. StandardSchemaValidationPipe— tayyor, lekin bitta status; kanon — o'zSxemaPipe(422/400).nestjs-zod5.5.0 peer bog'liqligi Nest 12 ni qo'llamaydi (2026-11-02):@nestjs/commonva@nestjs/swagger— 11 gacha; o'rnatilgan yo'l uni ortiqcha qiladi. Paket tanlashdan oldin —npm view <paket> peerDependencies.- Kanon o'lchovi: ro'yxat so'rovida Zod ~2.2× tez, bitta manba, yangi bog'liqliksiz — Zod qoldi; class-validator — o'lchab asoslangan muqobil.
Keyingi dars: Javobni serializatsiya qilish — kirishni tekshirdik, endi chiqish: parolHash kabi maydon javobga tushmasligi. Loyiha qadamida esa 415 ni guard bilan qaytaramiz va commit qilamiz.
Manbalar
- NestJS hujjati — Pipes (schema-based validation, Standard Schema): https://docs.nestjs.com/pipes
- Standard Schema spetsifikatsiyasi: https://standardschema.dev
- Zod 4 —
strictObject,coerce, lokalizatsiya: https://zod.dev - nestjs-zod — npm: https://www.npmjs.com/package/nestjs-zod va paket fayli https://unpkg.com/nestjs-zod@5.5.0/package.json (peerDependencies, ikkala manbada bir xil — 2026-11-02 holatida)
- Kanon (IlmHamroh, 2026-10-27): Zod va class-validator variantlari — HTTP yuk o'lchovi (autocannon, mediana), bitta tana tekshiruvi (200 000 aylana), 33 so'rovli solishtiruv, bog'liqliklar. Demo (
StandardSchemaValidationPipe,nestjs-zodva npm xatosi), oraliq holatdagi testlar — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, zod 4.6.7, nestjs-zod 5.5.0, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!