IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API29/51-dars20 daqiqa
Mundarija (26)

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 Number va Boolean tuzoqlarini ko'rasiz.
  • body, query, params uchun bitta qayta ishlatiladigan tekshiruv middleware'ini yozasiz.
  • vazifalar-api dagi 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:

bash
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/vazifalar ga {"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:

js
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:

text
"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' ] true

Har 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 satr true.
  • 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=0 va ?limit=1e9 da 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:

  1. Birinchi xatoda to'xtaydi. Uchta maydon xato bo'lsa — mijoz uchta so'rov bilan bilib oladi.
  2. Tur alohida yoziladi. type YangiVazifa = {...} va tekshiruv — ikki joyda; biri o'zgarsa, ikkinchisi esdan chiqadi.
  3. Takror. Har endpoint o'z if larini 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):

ts
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:

text
{ 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:

  1. 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.
  2. Hamma xato birga — uchta muammo, bitta javob (Problem Details xatolar massivi).
  3. strictObject — oq ro'yxat: rol kabi kutilmagan maydon xato. Oddiy z.object uni jim olib tashlardi — bu ham xavfsiz, lekin mijoz xatosini (bajarlidi deb yozilgan maydon) yashiradi.
  4. joy — aniq yo'l (body.matn), frontend xabarni kerakli maydon ostiga qo'ya oladi.
  5. 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):

ts
// 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:

ts
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):

ts
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.

  1. Zod'ni prod bog'liqlik sifatida o'rnating — aniq versiya bilan:
bash
npm i --save-exact zod@4.6.7

Kanon: 1 paket, diskda 5.9 MB; serverni ishga tushirish ~100 ms ga sekinlashdi (zod importi; t30 o'lchovi bilan mos).

  1. src/umumiy/tekshir.ts — tekshir(sxema, qiymat, joy) va tekshirib(...) (yuqorida). Xato klasslari hozircha HttpXato (422/400); keyingi darslarda ValidatsiyaXatosi va ParametrXatosi ga aylanadi (xato iyerarxiyasi).
  2. src/vazifalar/sxema.ts — birinchi sxemalar: YangiVazifaSxemasi, ToliqVazifaSxemasi, OzgarishSxemasi (.refine — kamida bitta maydon), IdSxemasi.
  3. Marshrutlarda qo'lda *Tekshir chaqiruvlari o'rniga tekshirib(...). vazifa.ts dagi yangiVazifaniTekshir, ozgarishniTekshir — o'chiriladi.
  4. 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:

bash
curl -X POST $A/v1/vazifalar -H 'Content-Type: application/json' \
  -d '{"matn":"","bajarildi":"ha","ortiqcha":1}'
curl $A/v1/vazifalar/abc
text
{"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:

  • limit 1..100 butun son —
  • Vazifa 42 mavjudmi —
  • matn 200 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
js
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:

text
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
js
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:

text
{ 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 ValidationPipe bilan, 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/Boolean tuzoqlari; avval shakl, keyin aylantirish.
  • Tekshiruv natijasi — tiplangan qiymat; hamma xato bir javobda, joy yo'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.query getter) — expressjs.com/en/guide/migrating-5.html
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Backend validatsiya: trust boundary, sintaktik va semantik tekshiruv, qayta ishlatiladigan middleware — IlmHamroh