Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ishonch chegarasi
- 2.1 Nima tashqaridan keladi
- 2.2 Ikki xil tekshiruv
- 3. Validatsiya va sanitizatsiya
- 3.1 Ikkalasining farqi
- 3.2 Query satrlari — hammasi satr
- 4. Sxema bilan tekshiruv
- 4.1 Nega qo'lda emas
- 4.2 Bitta tekshiruv funksiyasi
- 5. Qayta ishlatiladigan middleware
- 5.1 Muammo: req.query ga yozib bo'lmaydi
- 5.2 Yechim: natija argument bo'lib keladi
- 5.3 Har marshrutda nima tekshiriladi
- 6. Hujumchi nigohi
- 7. API qadami: tekshiruv middleware'i (holat 34, 1-qism)
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi qatlam?
- 2-mashq (o'rta): qat'iy query o'quvchilar
- 3-mashq (qiyin): middleware ko'rinishida
- 4-mashq: API qadami
- 10. Real ishda
- Xulosa
- Manbalar
Backend validatsiya: trust boundary, sintaktik va semantik tekshiruv, qayta ishlatiladigan middleware
Qisqacha: Serverga tashqaridan keladigan har qiymat — tana, query, yo'l parametri, sarlavha, cookie — ishonchsiz. Frontend'dagi tekshiruv serverni himoya qilmaydi: curl yoki o'z skripti bilan har kim istalgan narsani yubora oladi. Shuning uchun tekshiruv ishonch chegarasida (trust boundary) — ma'lumot server ichiga kirgan joyda, marshrut boshida bajariladi. Sintaktik tekshiruv (tur, uzunlik, format, ortiqcha maydon) — sxema bilan, middleware'da; semantik tekshiruv (vazifa mavjudmi, ro'yxat to'lmaganmi) — xizmat qatlamida. Natija — tiplangan, tozalangan qiymat; noto'g'ri tana — 422, noto'g'ri query yoki params — 400, hamma xato bitta javobda.
Bu darsda
- Ishonch chegarasi nima va nega frontend tekshiruvi yetarli emasligini tushuntira olasiz.
- Sintaktik va semantik validatsiyani ajratib, har birini to'g'ri qatlamga qo'yasiz.
- Validatsiya va sanitizatsiya farqini bilasiz; query satrlaridagi
NumbervaBooleantuzoqlarini ko'rasiz. body,query,paramsuchun bitta qayta ishlatiladigan tekshiruv middleware'ini yozasiz.vazifalar-apidagi qo'lda tekshiruvni sxemaga asoslangan tekshiruvga ko'chirishni boshlaysiz.
Oldin bilishingiz kerak: Middleware va request lifecycle, Zod va turni sxemadan chiqarish, Xato javoblari: Problem Details, API dizayn konvensiyalari.
1. Nega bu kerak?
22-qismdagi frontend'da forma bor: matn bo'sh bo'lsa "Saqlash" tugmasi bosilmaydi, 100 belgidan oshsa — yozib bo'lmaydi. Frontend dasturchisi xotirjam: "noto'g'ri ma'lumot serverga bormaydi".
Endi Sardor terminalni ochadi:
curl -X POST $A/v1/vazifalar -H 'Content-Type: application/json' \
-d '{"matn": "", "bajarildi": "ha", "rol": "admin"}'Brauzer yo'q, forma yo'q, frontend tekshiruvi yo'q. Server bu tanani qanday qabul qiladi? Agar tekshirmasa — bo'sh vazifa, "ha" satrli bajarildi (if (v.bajarildi) — true bo'lib chiqadi!) va kim biladi rol maydoni bilan nima bo'ladi.
O'xshatish: aeroport. Yo'lovchi uyda chamadonini o'zi tekshirdi — bu foydali, u vaqtini tejaydi. Lekin aeroport uning so'ziga ishonmaydi: kirishda hammasi rentgendan o'tadi. Chegara — rentgen. Ichkariga o'tgan narsa tekshirilgan deb hisoblanadi. Frontend tekshiruvi — uyda tekshirish (qulaylik). Server tekshiruvi — rentgen (xavfsizlik).
2. Ishonch chegarasi
2.1 Nima tashqaridan keladi
Ishonch chegarasi (trust boundary) — ma'lumot siz nazorat qilmaydigan joydan nazorat qiladigan joyga o'tadigan nuqta. Server uchun tashqi manbalar:
| Manba | Express'da | Misol hujum |
|---|---|---|
| tana | req.body |
noto'g'ri tur, ortiqcha maydon, 10 MB satr |
| query | req.query |
?limit=1000000, ?teg=a&teg=b (massiv) |
| yo'l parametri | req.params |
/vazifalar/abc, /vazifalar/-1 |
| sarlavhalar | req.get(...) |
soxta X-Request-Id, juda uzun Idempotency-Key |
| cookie | req.cookies |
28-qismda |
| boshqa API javobi | await fetch(...) |
CBU API javobi |
| env va config | process.env |
env validatsiyasi |
Oxirgi ikki qatorga qarang: boshqa servis va konfiguratsiya ham "tashqi". valyuta CLI'da CBU API javobini royxatniOqi bilan tekshirgan edik — o'sha g'oya.
Chegaradan ichkaridagi kod (xizmat, ombor) esa tekshirilgan, tiplangan qiymat bilan ishlaydi va qayta tekshirmaydi.
flowchart LR
T[Tashqi: brauzer, curl, servis] --> C{Chegara: sxema}
C -->|noto'g'ri| E[400 / 422 Problem Details]
C -->|tiplangan qiymat| X[Xizmat: biznes qoidalari]
X -->|404, 409, 412| E
X --> O[Ombor]2.2 Ikki xil tekshiruv
| Sintaktik (shakl) | Semantik (ma'no) | |
|---|---|---|
| Savol | "bu to'g'ri ko'rinishdagi ma'lumotmi?" | "bu holatda bu amal mumkinmi?" |
| Misollar | matn satr, 1–200 belgi; limit 1..100 butun son; ortiqcha maydon yo'q |
vazifa 99 mavjudmi; ro'yxatda 1000 dan kam vazifa bormi; If-Match versiyasi mosmi |
| Kerakli ma'lumot | faqat so'rovning o'zi | ombor holati (DB) |
| Qayerda | marshrut boshida — middleware, sxema | xizmat qatlami |
| Status | tana — 422, query/params — 400 | 404, 409, 412 |
Ajratishning sababi: sintaktik tekshiruv arzon va hech qanday DB so'rovisiz bajariladi. Noto'g'ri so'rov xizmatga yetib ham bormaydi. Semantik tekshiruv esa holatni biladi — uni xizmat qiladi, chunki u HTTP'ni emas, biznes qoidalarini biladi (qatlamli tuzilma).
Kanon status tanlovi ham shundan: tananing mazmuni noto'g'ri — 422 (JSON to'g'ri, lekin qiymatlar qoidaga mos emas), manzil noto'g'ri (?limit=abc, /vazifalar/abc) — 400 (status kodlar darsi).
Tekshirib ko'ring:
POST /v1/vazifalarga{"matn": "Non olish"}keldi, lekin ro'yxatda allaqachon 1000 ta vazifa bor. Bu sintaktik xatomi yoki semantik? Status qaysi?
Javob
Semantik. Tana shakli to'g'ri — sxema uni o'tkazadi. Faqat ombor holatini bilgan xizmat "ro'yxat to'lgan" deya oladi. Kanonda bu royxat-toldi, 409 Conflict — so'rov resursning joriy holati bilan ziddiyatda.
3. Validatsiya va sanitizatsiya
3.1 Ikkalasining farqi
Validatsiya — "qabul qilamizmi?" degan savol: ha yoki yo'q (xato bilan). Sanitizatsiya (tozalash) — qiymatni xavfsiz yoki bir xil shaklga keltirish: bo'shliqlarni olib tashlash, kichik harfga o'tkazish.
Kanonda ikkalasi ham bor:
matn—trim()qilinadi (sanitizatsiya), keyin 1–200 tekshiriladi (validatsiya)." "→""→ xato.?teg=#UY—#olib tashlanadi va kichik harfga o'tkaziladi →uy.
Bir qoida: HTML'ni "tozalab" saqlamang. <script> ni o'chirib saqlash — urinish ko'pincha aylanib o'tiladi. Matn qanday kelsa shunday (validatsiyadan o'tib) saqlanadi; xavfsizlik — chiqishda: React matnni o'zi escape qiladi, HTML kerak bo'lsa — DOMPurify (XSS darsi).
3.2 Query satrlari — hammasi satr
Query va yo'l parametrlari doim satr bo'lib keladi. Ularni songa yoki mantiqiy qiymatga aylantirish — ko'p tuzoqli joy. Bu blok brauzerda ham ishlaydi:
const samples = [
"20", "", " 12 ", "1e3", "0x10", "12abc", "-5", "2.5",
];
for (const s of samples) {
const shown = JSON.stringify(s).padEnd(8);
console.log(shown, Number(s), parseInt(s, 10));
}
const raw = "limit=20&teg=uy&teg=ish&bajarildi=false";
const q = new URLSearchParams(raw);
const flag = Boolean(q.get("bajarildi"));
console.log(q.get("teg"), q.getAll("teg"), flag);Konsolda:
"20" 20 20
"" 0 NaN
" 12 " 12 12
"1e3" 1000 1
"0x10" 16 0
"12abc" NaN 12
"-5" -5 -5
"2.5" 2.5 2
uy [ 'uy', 'ish' ] trueHar qatorda tuzoq bor:
Number("")→ 0:?limit=(bo'sh) jim 0 bo'ladi.Number("1e3")→ 1000,Number("0x10")→ 16 — ilmiy va o'n oltilik yozuv qabul qilinadi.parseInt("12abc")→ 12 — oxiridagi axlat jim tashlanadi.Boolean("false")→ true — bo'sh bo'lmagan har satrtrue.q.get("teg")faqat birinchisini beradi; Express 5 esa takrorlangan kalitni massiv qiladi (req obyekti):req.query.teg—"uy"ham,["uy","ish"]ham bo'lishi mumkin.
Xulosa: satrni songa aylantirishdan oldin shaklini tekshiring — /^\d{1,6}$/ — keyin Number. Mantiqiy qiymat — faqat "true" va "false" satrlari, boshqasi — xato. Kanondagi Butun va Mantiqiy sxemalari aynan shunday.
Tekshirib ko'ring: Kodda
const limit = Number(req.query.limit) || 20;yozilgan.?limit=abc,?limit=0va?limit=1e9da nima bo'ladi?
Javob
abc → NaN → || → 20 (xato jim yutildi). 0 → 0 → || → 20 (0 ham "yolg'on" — ehtimol to'g'ri, lekin tasodifan). 1e9 → 1 000 000 000 — chegara yo'q, server milliardta vazifani yig'ishga urinadi. To'g'risi: shakl (^\d{1,6}$), keyin oraliq (1..100), noto'g'ri bo'lsa — 400 va tushunarli xabar.
4. Sxema bilan tekshiruv
4.1 Nega qo'lda emas
Holat 05 dan beri vazifalar-api da qo'lda tekshiruv bor edi: yangiVazifaniTekshir(body) — if lar zanjiri. Uning uch kamchiligi:
- Birinchi xatoda to'xtaydi. Uchta maydon xato bo'lsa — mijoz uchta so'rov bilan bilib oladi.
- Tur alohida yoziladi.
type YangiVazifa = {...}va tekshiruv — ikki joyda; biri o'zgarsa, ikkinchisi esdan chiqadi. - Takror. Har endpoint o'z
iflarini yozadi.
Sxema bu uchalasini hal qiladi: tavsif bir joyda, xatolar hammasi yig'iladi, tur sxemadan olinadi. Zod'ni TypeScript qismida va formalarda ishlatgansiz — serverda u xuddi shunday ishlaydi.
4.2 Bitta tekshiruv funksiyasi
Quyidagi blok kanondagi tekshir() ning soddalashtirilgan nusxasi. U sxemani qiymatga qo'llaydi, xatolarni joy yo'llari bilan yig'adi va joyga qarab statusni tanlaydi. Blok Node'da Zod 4 bilan tekshiriladi (saytda ts + npm paket — «Ishga tushir» yo'q):
import { z } from "zod";
type Place = "body" | "query" | "params";
type FieldError = { joy: string; xabar: string };
class RequestError extends Error {
status: number;
errors: FieldError[];
constructor(status: number, errors: FieldError[]) {
super(errors[0]?.xabar ?? "noto'g'ri so'rov");
this.status = status;
this.errors = errors;
}
}
function check<S extends z.ZodType>(
schema: S, value: unknown, place: Place,
): z.output<S> {
const r = schema.safeParse(value);
if (r.success) return r.data;
const errors = r.error.issues.map((i) => ({
joy: [place, ...i.path.map(String)].join("."),
xabar: i.message,
}));
// tana mazmuni noto'g'ri — 422; manzil noto'g'ri — 400
throw new RequestError(place === "body" ? 422 : 400, errors);
}
const NewTask = z.strictObject({
matn: z.string({ error: "matn — satr" }).trim()
.min(1, { error: "matn 1–200 belgi" })
.max(200, { error: "matn 1–200 belgi" }),
bajarildi: z.boolean({ error: "bajarildi — true/false" })
.optional(),
});
const IdParams = z.strictObject({
id: z.string()
.regex(/^[1-9]\d{0,9}$/, { error: "id — musbat son" })
.transform(Number),
});
const ok = check(NewTask, { matn: " Non olish #uy " }, "body");
console.log(ok);
console.log(check(IdParams, { id: "42" }, "params"));
const inputs: [z.ZodType, unknown, Place][] = [
[NewTask, { matn: "", bajarildi: "ha", rol: "admin" }, "body"],
[IdParams, { id: "abc" }, "params"],
];
for (const [schema, value, place] of inputs) {
try {
check(schema, value, place);
} catch (e) {
if (!(e instanceof RequestError)) throw e;
console.log(e.status, e.errors);
}
}Konsolda:
{ matn: 'Non olish #uy' }
{ id: 42 }
422 [
{ joy: 'body.matn', xabar: 'matn 1–200 belgi' },
{ joy: 'body.bajarildi', xabar: 'bajarildi — true/false' },
{ joy: 'body', xabar: 'Unrecognized key: "rol"' }
]
400 [ { joy: 'params.id', xabar: 'id — musbat son' } ]Beshta narsaga qarang:
- Natija — tozalangan qiymat.
" Non olish #uy "→"Non olish #uy";"42"→42(son). Handler endi satr bilan emas, toza qiymat bilan ishlaydi. Bu g'oya "parse, don't validate" deb ataladi: tekshiruv faqat "ha/yo'q" demaydi — ishonchli turdagi yangi qiymat qaytaradi. - Hamma xato birga — uchta muammo, bitta javob (Problem Details
xatolarmassivi). strictObject— oq ro'yxat:rolkabi kutilmagan maydon xato. Oddiyz.objectuni jim olib tashlardi — bu ham xavfsiz, lekin mijoz xatosini (bajarlidideb yozilgan maydon) yashiradi.joy— aniq yo'l (body.matn), frontend xabarni kerakli maydon ostiga qo'ya oladi.- Status — joyga qarab:
body→ 422,params→ 400.
"Unrecognized key" — Zod'ning sukut xabari, inglizcha. Kanon z.config(z.locales.uz()) bilan o'zbekcha lokalni yoqadi (Zod 4 serverda).
5. Qayta ishlatiladigan middleware
5.1 Muammo: req.query ga yozib bo'lmaydi
Birinchi xayol — middleware tekshirsin va natijani req ga qaytarib yozsin: req.body = toza, req.query = toza. Express 5 da req.query — faqat o'qiladigan getter: unga yozish TypeError beradi (kanon t10 tajribasi, req obyekti darsi). Bundan tashqari, req.query ning TS turi baribir ParsedQs bo'lib qoladi — tekshirilgan tur yo'qoladi.
5.2 Yechim: natija argument bo'lib keladi
Kanon boshqacha yo'l tutdi: tekshirib(sxemalar, ishlovchi) — middleware va handler'ni birlashtiradi. Tekshirilgan qiymatlar handler'ga birinchi argument bo'lib, to'g'ri turi bilan keladi. To'liq src/umumiy/tekshir.ts (kanondan; qatorlar telefon uchun bo'lingan):
// tekshir.ts — chegarada tekshiruv (trust boundary): tashqaridan
// kelgan har qiymat Zod sxemasidan o'tadi.
// tekshir() — bitta qiymat (typed natija); tekshirib() — qayta
// ishlatiladigan middleware: body/query/params bir joyda
import { z } from "zod";
import type { Request, RequestHandler, Response } from "express";
import { ParametrXatosi, ValidatsiyaXatosi } from "./xatolar.ts";
import type { MaydonXatosi } from "./xatolar.ts";
// Zod 4 da o'zbekcha lokal bor (z.locales.uz) — sxemada o'z
// xabarimiz bo'lmasa shu ishlatiladi
z.config(z.locales.uz());
export type Joy = "body" | "query" | "params";
function xatolarga(joy: Joy, xato: z.ZodError): MaydonXatosi[] {
return xato.issues.map((i) => ({
joy: [joy, ...i.path.map(String)].join("."),
xabar: i.message,
}));
}
/** body → 422 (tana mazmuni), query/params → 400 (manzil noto'g'ri) */
export function tekshir<S extends z.ZodType>(
sxema: S, qiymat: unknown, joy: Joy,
): z.output<S> {
const n = sxema.safeParse(qiymat);
if (n.success) return n.data;
const xatolar = xatolarga(joy, n.error);
if (joy === "body") throw new ValidatsiyaXatosi(xatolar);
throw new ParametrXatosi(xatolar[0]?.xabar ?? "noto'g'ri", xatolar);
}
type Sxemalar = {
body?: z.ZodType; query?: z.ZodType; params?: z.ZodType;
};
type Chiqish<S extends Sxemalar> = {
[K in keyof S]: S[K] extends z.ZodType ? z.output<S[K]> : never;
};
/**
* Middleware + tipli handler:
* `r.post(tekshirib({ body: YangiSxema }, async ({ body }, req, res) => …))`.
* Express 5 da req.query — faqat getter, yozib bo'lmaydi; shuning
* uchun natija handler'ga argument bo'lib keladi
*/
export function tekshirib<S extends Sxemalar>(
sxemalar: S,
ishlovchi: (kirish: Chiqish<S>, req: Request, res: Response)
=> Promise<void>,
): RequestHandler {
return async (req, res) => {
const kirish: Record<string, unknown> = {};
for (const joy of ["params", "query", "body"] as const) {
const sxema = sxemalar[joy];
if (sxema !== undefined) {
kirish[joy] = tekshir(sxema, req[joy], joy);
}
}
await ishlovchi(kirish as Chiqish<S>, req, res);
};
}Bu holat 34 dagi yakuniy shakl. ParametrXatosi va ValidatsiyaXatosi (./xatolar.ts) — xato iyerarxiyasi darsida yoziladi. Hozircha (holat 27 dagi HttpXato bilan) tekshir oxiridagi ikki qator shunday bo'ladi — loyiha tip va testlardan o'tadi:
import { HttpXato } from "./http-xato.ts";
// ...
if (joy === "body") {
throw new HttpXato(422, xatolar.length === 1
? (xatolar[0]?.xabar ?? "")
: `${String(xatolar.length)} ta maydon noto'g'ri`,
{ kod: "validatsiya", sarlavha: "So'rov ma'lumoti noto'g'ri",
xatolar });
}
throw new HttpXato(400, xatolar[0]?.xabar ?? "noto'g'ri",
{ kod: "parametr", sarlavha: "Parametr noto'g'ri", xatolar });MaydonXatosi turi esa hozircha ./http-xato.ts dan import qilinadi.
Kesim tekshiruvi: tip 0, lint 0, testlar 31/31 — biz ishga tushirdik (Node 24.21.0; holat 27 + shu ko'rinishdagi tekshir.ts). Marshrutlar tekshirib ga ko'chirilgandan keyingi to'liq kesimni alohida ishga tushirmadik — u keyingi darsning kesimida (35/35) tekshirilgan. Marshrutlarni tekshirib ga o'tkazganingizdan keyin eski testlardagi joy kutishlari ("body" → "body.matn") o'zgaradi — 4-mashqda. z.config(z.locales.uz()) qatori keyingi darsda tushuntiriladi — uni hozirdanoq qo'yish mumkin.
Ishlatilishi (marshrutda):
r.route("/:id").patch(jsonKerak,
tekshirib({ params: IdSxemasi, body: OzgarishSxemasi },
async ({ params, body }, req, res) => {
// params.id — number;
// body — { matn?: string; bajarildi?: boolean }
const v = await xizmat.yangila(params.id, body, ifMatch(req));
vazifaSarlavhalari(res, v);
res.json(v);
}));Chiqish<S> — mapped type (TS darsi): har berilgan sxema uchun uning z.output turi. params.id ni TypeScript number deb biladi — Number(req.params.id) qo'lda yozilmaydi.
Tartib ham ahamiyatli: params → query → body. Avval manzil tekshiriladi: /vazifalar/abc ga noto'g'ri tana yuborilsa ham, mijoz birinchi navbatda "id noto'g'ri" (400) ni oladi.
PATCH /v1/vazifalar/abc ga {"matn": ""} keldi deylik. So'rov qadamma-qadam shunday yuradi:
| Qadam | Middleware | Natija |
|---|---|---|
| 1 | sorovId, helmet, cors, express.json() |
so'rov ID berildi, tana JSON sifatida o'qildi: { matn: "" } |
| 2 | jsonKerak |
Content-Type to'g'ri — next() |
| 3 | tekshirib → params |
"abc" regex'dan o'tmadi — ParametrXatosi tashlandi |
| 4 | tekshirib → query, body |
bajarilmaydi — xato birinchi qadamdayoq chiqdi |
| 5 | xato ishlovchi | 400 Problem Details, joy: "params.id" |
Handler (xizmat.yangila) umuman chaqirilmadi — noto'g'ri so'rov xizmatga yetib bormadi.
Xato tashlanganda Express 5 uni o'zi xato ishlovchisiga uzatadi (Express 5 async xatolar) — try/catch kerak emas.
5.3 Har marshrutda nima tekshiriladi
| Marshrut | params | query | body |
|---|---|---|---|
GET /v1/vazifalar |
— | RoyxatQuerySxemasi |
— |
POST /v1/vazifalar |
— | — | YangiVazifaSxemasi |
GET /v1/vazifalar/:id |
IdSxemasi |
— | — |
PUT /v1/vazifalar/:id |
IdSxemasi |
— | ToliqVazifaSxemasi |
PATCH /v1/vazifalar/:id |
IdSxemasi |
— | OzgarishSxemasi |
PUT /v1/vazifalar |
— | — | ToplamSxemasi |
Sarlavhalar (Idempotency-Key, If-Match, X-Request-Id) — o'z middleware'larida regex bilan tekshiriladi (oldingi darslarda ko'rgansiz). Ular ham chegara.
6. Hujumchi nigohi
| Hujum | Qanday ko'rinadi | Himoya |
|---|---|---|
| Mass assignment | {"matn":"x","id":1,"versiya":999} — server yozmasligi kerak maydonlar |
strictObject — oq ro'yxat; kutilmagan maydon 422 (B-130) |
| Tur aralashtirish | {"bajarildi":"false"} — satr if da true |
z.boolean() — faqat haqiqiy true/false |
| Massiv kutilmagan joyda | ?q=a&q=b → ["a","b"], keyin q.toLowerCase() → TypeError → 500 |
Bitta("q") — faqat satr, massiv 400 |
| Ulkan qiymat | matn 10 MB, limit=1e9 |
express.json({ limit: "16kb" }) + .max(200) + oraliq |
| Prototip ifloslanishi | {"__proto__":{"admin":true}} |
JSON.parse __proto__ ni oddiy kalit qiladi; strictObject uni noma'lum kalit sifatida rad etadi; obyektlarni chuqur birlashtirish (merge) — ehtiyot |
| Operator injeksiyasi | ?filtr[$gt]= (qs "extended" parser obyekt yasaydi) |
Express 5 "simple" parser — obyekt yasamaydi; sxema faqat satr kutadi |
Oxirgi ikki qator — NoSQL va JS'ga xos hujumlar. Ularni injeksiya darsida chuqur ko'rasiz. Asosiy himoya bitta: kutilgan shaklni aniq tavsiflash — qolgani rad.
7. API qadami: tekshiruv middleware'i (holat 34, 1-qism)
Bu qadam holat 27 dan boshlanadi va keyingi bir necha dars davom etadi. Zod sxemalari (keyingi dars), xato iyerarxiyasi, global xato ishlovchi va jarayon xatolari — hammasi holat 34 da. Commit bitta, jarayon xatolari darsi oxirida. Hozircha commit qilmang.
- Zod'ni prod bog'liqlik sifatida o'rnating — aniq versiya bilan:
npm i --save-exact zod@4.6.7Kanon: 1 paket, diskda 5.9 MB; serverni ishga tushirish ~100 ms ga sekinlashdi (zod importi; t30 o'lchovi bilan mos).
src/umumiy/tekshir.ts—tekshir(sxema, qiymat, joy)vatekshirib(...)(yuqorida). Xato klasslari hozirchaHttpXato(422/400); keyingi darslardaValidatsiyaXatosivaParametrXatosiga aylanadi (xato iyerarxiyasi).src/vazifalar/sxema.ts— birinchi sxemalar:YangiVazifaSxemasi,ToliqVazifaSxemasi,OzgarishSxemasi(.refine— kamida bitta maydon),IdSxemasi.- Marshrutlarda qo'lda
*Tekshirchaqiruvlari o'rnigatekshirib(...).vazifa.tsdagiyangiVazifaniTekshir,ozgarishniTekshir— o'chiriladi. - Turlar sxemadan:
type YangiVazifa = z.output<typeof YangiVazifaSxemasi>— qo'lda takrorlanmaydi.
Holat 34 dagi natija (kanon curl, qisqartirilgan). Chiqishda ikki narsaga qarang: birinchi javobdagi xatolar massivida nechta element bor va har birining joy qiymati qanday:
curl -X POST $A/v1/vazifalar -H 'Content-Type: application/json' \
-d '{"matn":"","bajarildi":"ha","ortiqcha":1}'
curl $A/v1/vazifalar/abc{"type":"https://vazifalar-api.example/xatolar/validatsiya","title":"So'rov ma'lumoti noto'g'ri","status":422,"detail":"3 ta maydon noto'g'ri","instance":"/v1/vazifalar","kod":"validatsiya","xatolar":[{"joy":"body.matn","xabar":"matn 1–200 belgi bo'lishi kerak"},{"joy":"body.bajarildi","xabar":"bajarildi — true yoki false"},{"joy":"body","xabar":"Noma’lum kalit: \"ortiqcha\""}]}
{"type":"https://vazifalar-api.example/xatolar/parametr","title":"Parametr noto'g'ri","status":400,"detail":"id — musbat butun son","instance":"/v1/vazifalar/abc","kod":"parametr","xatolar":[{"joy":"params.id","xabar":"id — musbat butun son"}]}Nima bo'ldi? Tanadagi uchta muammo bitta 422 javobda, har biri o'z joy yo'li bilan qaytdi. Ikkinchi so'rovda tana yo'q — faqat yo'l parametri xato, shuning uchun 400.
Holat 24 dagi javob bilan solishtiring: u yerda bitta xato va "joy":"body" edi. Endi uchta xato, aniq yo'llar bilan. (Uchinchi xabardagi ’ — Zod o'zbekcha lokalining belgisi; bizning matnlarda '. Bitta javobda aralash — kanon buni halol qayd etgan.)
8. Ko'p uchraydigan xatolar
1. "Frontend tekshiradi-ku". Frontend — qulaylik, chegara emas. curl uni chetlab o'tadi.
2. Tekshiruvni xizmat ichida tarqatish. Har funksiya o'zicha if (typeof x !== "string") — takror va bo'shliqlar. Shakl — chegarada, bir marta.
3. Ichki kodda qayta-qayta tekshirish. Chegaradan o'tgan qiymat tiplangan; ichkarida yana tekshirish — kodni chalkashtiradi va "qaysi tekshiruv asosiy?" degan savol tug'diradi.
4. Number() va Boolean() bilan query o'qish. Boolean("false") === true, Number("") === 0.
5. Ortiqcha maydonni jim qabul qilish. Mass assignment eshigi yoki mijoz xatosini yashirish.
6. Validatsiya xatosida 500. req.query.q.toLowerCase() massivda yiqiladi — bu 400 bo'lishi kerak edi. Turni tekshirmasdan ishlatish — 500 manbai.
7. HTML'ni kirishda "tozalash". Chiqishda escape qiling.
9. Mashqlar
Yechimlar kurs/mashqlar/25/29-validatsiya/ da.
1-mashq (oson): qaysi qatlam?
Har tekshiruv uchun "chegara" (sxema) yoki "xizmat" deb yozing:
limit1..100 butun son —- Vazifa 42 mavjudmi —
matn200 belgidan oshmasin —- Ro'yxatda 1000 dan kam vazifa —
- Tanada noma'lum maydon yo'q —
Yechim
- chegara — faqat so'rovning o'zi kerak.
- xizmat — ombor holatini biladi (404).
- chegara — shakl.
- xizmat — holatga bog'liq (409).
- chegara —
strictObject.
2-mashq (o'rta): qat'iy query o'quvchilar
Ikki funksiya yozing (Zod'siz): parseIntParam(raw, name, min, max) — faqat ^\d{1,6}$ shaklidagi satrni qabul qilsin va oraliqni tekshirsin; parseBoolParam(raw, name) — faqat "true"/"false". Massiv kelsa (["1","2"]) — "bir marta" xatosi. undefined — undefined qaytaradi. Xato — Error matni bilan. Bu toza JS — «Ishga tushir» bilan ishlaydi.
Yechim
function single(raw, name) {
if (Array.isArray(raw)) {
throw new Error(`${name} bir marta berilsin`);
}
return raw;
}
function parseIntParam(raw, name, min, max) {
const s = single(raw, name);
if (s === undefined) return undefined;
if (!/^\d{1,6}$/.test(s) || Number(s) < min || Number(s) > max) {
const range = `${min}..${max}`;
throw new Error(`${name} — ${range} oralig'idagi butun son`);
}
return Number(s);
}
function parseBoolParam(raw, name) {
const s = single(raw, name);
if (s === undefined) return undefined;
if (s !== "true" && s !== "false") {
throw new Error(`${name} — true yoki false`);
}
return s === "true";
}
const cases = [
() => parseIntParam("20", "limit", 1, 100),
() => parseIntParam("1e3", "limit", 1, 100),
() => parseIntParam("", "limit", 1, 100),
() => parseIntParam(["1", "2"], "limit", 1, 100),
() => parseBoolParam("false", "bajarildi"),
() => parseBoolParam("ha", "bajarildi"),
() => parseBoolParam(undefined, "bajarildi"),
];
for (const run of cases) {
try {
console.log(run());
} catch (error) {
console.log("XATO:", error.message);
}
}Konsolda:
20
XATO: limit — 1..100 oralig'idagi butun son
XATO: limit — 1..100 oralig'idagi butun son
XATO: limit bir marta berilsin
false
XATO: bajarildi — true yoki false
undefined"" va "1e3" — regex'dan o'tmaydi, Number gacha yetib bormaydi.
3-mashq (qiyin): middleware ko'rinishida
Express'siz model yozing: validate(schemas, handler) — schemas — { body?, query?, params? }, har biri (value) => { ok, value } | { ok: false, errors: [{ path, message }] } funksiya. U (req) => { status, body } qaytarsin: hamma joy tekshiriladi (params → query → body), birinchi xatoli joyda to'xtamaydi, hamma xatolarni yig'adi; tanada xato bo'lsa 422, faqat query/params da bo'lsa 400. Handler tekshirilgan qiymatlarni oladi.
Yechim
function validate(schemas, handler) {
return (req) => {
const input = {};
const errors = [];
for (const place of ["params", "query", "body"]) {
const schema = schemas[place];
if (!schema) continue;
const r = schema(req[place]);
if (r.ok) {
input[place] = r.value;
} else {
for (const e of r.errors) {
const joy = [place, e.path].filter(Boolean).join(".");
errors.push({ joy, xabar: e.message });
}
}
}
if (errors.length) {
const inBody = errors.some((e) => e.joy.startsWith("body"));
const status = inBody ? 422 : 400;
return { status, body: { xatolar: errors } };
}
return handler(input);
};
}
const idSchema = (p) => /^[1-9]\d*$/.test(p?.id ?? "")
? { ok: true, value: { id: Number(p.id) } }
: { ok: false, errors: [{ path: "id", message: "musbat son" }] };
const bodySchema = (b) => typeof b?.matn === "string" && b.matn.trim()
? { ok: true, value: { matn: b.matn.trim() } }
: { ok: false, errors: [{ path: "matn", message: "bo'sh" }] };
const update = validate({ params: idSchema, body: bodySchema },
({ params, body }) => ({
status: 200, body: { id: params.id, ...body },
}));
console.log(update({ params: { id: "7" }, body: { matn: " Non " } }));
const bad = { id: "x" };
console.log(JSON.stringify(update({ params: bad, body: {} })));
const onlyId = update({ params: bad, body: { matn: "a" } });
console.log(JSON.stringify(onlyId));Konsolda:
{ status: 200, body: { id: 7, matn: 'Non' } }
{"status":422,"body":{"xatolar":[{"joy":"params.id","xabar":"musbat son"},{"joy":"body.matn","xabar":"bo'sh"}]}}
{"status":400,"body":{"xatolar":[{"joy":"params.id","xabar":"musbat son"}]}}Kanondagi tekshirib birinchi xatoli joyda to'xtaydi (tekshir xato tashlaydi) — bu yechim hammasini yig'adi. Ikkala yondashuv ham to'g'ri; yig'ish mijozga ko'proq ma'lumot beradi, to'xtash esa soddaroq.
4-mashq: API qadami
O'z vazifalar-api ingizda «API qadami» bo'limidagi 1–5 qadamlarni bajaring: zod@4.6.7, tekshir.ts, birinchi sxemalar, marshrutlarda tekshirib(...), qo'lda tekshiruv funksiyalarini o'chirish. curl bilan {"matn":"","bajarildi":"ha","ortiqcha":1} yuboring — javobda uchta xato bo'lsin. Mavjud testlarning joy kutishlari ("body" → "body.matn") o'zgaradi — ularni yangilang. Commit qilmang.
10. Real ishda
- OWASP API Security Top 10 da "Broken Object Property Level Authorization" (mass assignment) va "Unrestricted Resource Consumption" (chegarasiz qiymatlar) — ikkalasining birinchi himoyasi chegaradagi sxema (OWASP API darsi).
- NestJS'da (30-qism) shu g'oya
ValidationPipebilan, Fastify'da — har marshrutga JSON Schema bilan qurilgan. - Katta jamoalarda sxemalar frontend va backend o'rtasida umumiy paketga chiqariladi — bitta qoida, ikki joyda (API qatlami va type-safe kontrakt).
- Intervyu: "Frontend validatsiya qilsa, backend'da kerakmi?" — ha, chegara; "400 va 422 farqi?" — so'rov shakli va mazmuni.
Xulosa
- Tashqaridan kelgan har qiymat — ishonchsiz; tekshiruv ishonch chegarasida, bir marta.
- Sintaktik (shakl) — sxema bilan, middleware'da; semantik (holat) — xizmatda.
- Sanitizatsiya (
trim, kichik harf) — validatsiyadan oldin; HTML — chiqishda escape. - Query va params — satr:
Number/Booleantuzoqlari; avval shakl, keyin aylantirish. - Tekshiruv natijasi — tiplangan qiymat; hamma xato bir javobda,
joyyo'llari bilan; tana — 422, manzil — 400.
Keyingi dars: Zod 4 serverda — query uchun transform va pipe, discriminatedUnion, o'zbekcha lokal, prettifyError, toJSONSchema va zod/mini serverda nega sekinroq chiqqani.
Manbalar
- OWASP Input Validation Cheat Sheet — cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
- OWASP Mass Assignment Cheat Sheet — cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
- Zod 4 hujjati — zod.dev
- Express 5 "Migrating to Express 5" (
req.querygetter) — expressjs.com/en/guide/migrating-5.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!