Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. API qatlami: so'rovlar bitta joyda
- 2.1 Nima uchun bitta joy
- 2.2 Qatlam nima qiladi
- 3. Tur — va'da, tekshiruv emas
- 3.1 as hech narsani tekshirmaydi
- 3.2 Chegarada tekshirish
- 4. shartnoma() — har metod tekshirilgan tur qaytaradi
- 4.1 Bitta yordamchi
- 4.2 Xato normallashtirish: bitta tur, bir nechta kod
- 5. Backend bilan kontrakt: OpenAPI va kod generatsiyasi
- 5.1 Sxemani kim yozadi?
- 5.2 To'liq TypeScript stekida — tRPC
- 6. Vazifalar qadami: Zod kontrakti (holat 12)
- 6.1 Nima o'zgaradi
- 6.2 api-sxema.ts
- 6.3 api.ts — shartnoma va metodlar
- 6.4 Narxi: hajm, CSP va jitless
- 6.5 Tekshiruvlar
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi kod?
- 2-mashq (o'rta): buyurtma sxemasi
- 3-mashq (qiyin): API qatlami bilan sahifa
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
API qatlami va type-safe kontrakt: Zod bilan server javobini tekshirish, xatolarni normallashtirish va OpenAPI
Qisqacha: Barcha server so'rovlari bitta API qatlamida yashaydi (
api.ts): komponentlarfetchni bilmaydi,api.royxat()ni chaqiradi. Server javobi aslidaunknown— TypeScript turi (as Vazifa[]) uni tekshirmaydi. Shuning uchun har metod javobni sxema (Zod) bilan ish vaqtida tekshiradi va aniq tur qaytaradi; mos kelmasa — bitta turdagi xato (ApiXatosi, kodJAVOB) "qaysi maydon buzuq" degan matn bilan. Sxema ham tekshiruv, ham tur (z.infer) — bitta manba. Backend OpenAPI bersa, turlar va klient kodini generatsiya qilish mumkin: openapi-typescript, orval, Hey API.
Bu darsda
- API qatlami nima uchun kerakligini va unda nima bo'lishini (manzil, sarlavhalar, xatolar, tekshiruv) bilasiz.
asbilan yozilgan tur ish vaqtida hech narsani tekshirmasligini jonli ko'rasiz.- Zod sxemasi bilan javobni tekshiradigan
shartnoma()funksiyasini yozasiz va xato yo'lini ("narx: …") chiqarasiz. - Xatolarni bitta turga keltirasiz (tarmoq, 404, 500, buzuq javob) va UI uni qanday ko'rsatishini hal qilasiz.
- OpenAPI asosida kod generatsiyasi (openapi-typescript, orval, Hey API) nima ekanini bilasiz;
vazifalarga Zod kontraktini qo'shasiz.
Oldin bilishingiz kerak: API klient qatlami, API ma'lumotlarini tiplash, Zod va turni sxemadan chiqarish, Server state vs client state.
1. Nega bu kerak?
Oldingi darsda server state — kesh ekanini ko'rdik. Keyingi darsdan boshlab uni TanStack Query boshqaradi. Lekin kesh ichiga nima tushadi? Server nima yuborsa — o'sha. Agar server noto'g'ri narsa yuborsa?
«Bahor» da shunday bo'ldi. Backend dasturchisi yangi versiyada narxni satr qilib yubora boshladi: "price": "35000". Frontend'da tur price: number deb yozilgan edi — TypeScript xursand. Savatda esa:
35 000 + 5 000 = 350005000 so'm
Raqamlar qo'shilmadi — satrlar ulandi. Hech qanday xato yo'q, mijoz esa 350 million so'mlik hisob ko'rdi. Muammo serverda edi, lekin uni frontend sezishi mumkin edi — chegarada, javob kelgan zahoti.
Hayotiy o'xshatish — oshxonaga keladigan mahsulot. Yaxshi oshpaz qopni ochmasdan qozonga to'kmaydi: avval qaraydi — "guruch shu, chiqitsiz, buzilmagan". Hujjatda "guruch, 10 kg" deb yozilgani (TypeScript turi) — va'da, xolos. Tekshiruv — eshik oldida (API qatlami), qozon oldida emas (komponent).
2. API qatlami: so'rovlar bitta joyda
2.1 Nima uchun bitta joy
API klient qatlami darsida fetch o'ramini yozgansiz. React ilovada bu yanada muhim: so'rov komponentda, loader'da, action'da, keyinchalik TanStack Query'da bo'ladi. Har biri fetch(…) ni o'zi yozsa:
- manzil (
https://…/api/vazifalar) o'nta joyda — o'zgarsa, o'nta tuzatish; res.oktekshiruvi qayerdadir unutiladi — 500 javobi "muvaffaqiyat" bo'lib o'tadi;- xatolar har xil: biri
TypeError, biri matn, biri{ error }; - javob tekshiruvi — umuman yo'q.
API qatlami — frontend va backend orasidagi yagona eshik. vazifalar da u 13-qismdan beri bor (src/api.ts):
flowchart LR
K["Komponent,<br/>loader, action"] -->|"api.royxat()"| A["api.ts"]
A -->|"fetch + signal,<br/>vaqt chegarasi"| S[("Server")]
S -->|"unknown JSON"| A
A -->|"sxema tekshiruvi"| T{"mosmi?"}
T -- ha --> K2["VazifaMalumoti[]"]
T -- yo'q --> X["ApiXatosi (JAVOB)"]Chap tomonda — faqat tiplangan qiymat yoki bitta turdagi xato. fetch, status kodlar, JSON — hammasi qatlam ichida.
2.2 Qatlam nima qiladi
| Vazifa | vazifalar dagi api.ts |
|---|---|
| asosiy manzil, sarlavhalar | apiKlient({ asosiyUrl, kalit }) |
| vaqt chegarasi va bekor qilish | 8 s, AbortSignal (KUTISH_MS) |
| status → xato kodi | 404 → TOPILMADI, 5xx → SERVER |
| tarmoq xatosi → xato kodi | TARMOQ, VAQT, BEKOR |
| javob shakli tekshiruvi | bugun qo'shiladi — Zod |
| metodlar | royxat, qosh, yangila, ochir, almashtir |
Axios bilan ham shunday qatlam yoziladi: axios.create({ baseURL }) va interceptor'lar (Axios). Vosita muhim emas — muhimi, qatlam bitta.
3. Tur — va'da, tekshiruv emas
3.1 as hech narsani tekshirmaydi
API ma'lumotlarini tiplash darsidan eslang: res.json() ning natijasi — any (aslida unknown). Ko'p kodda shunday yoziladi: const dish = (await res.json()) as Dish. TypeScript as ni ishonch deb qabul qiladi — tekshirmaydi. Natija oynasida o'sha voqea:
import { useEffect, useState } from "react";
// Server yangi versiyada narxni satr qilib yubordi
const fetchDish = () =>
new Promise((resolve) =>
setTimeout(() => resolve({ name: "Osh", price: "35000" }), 200),
);
const DELIVERY = 5000;
export default function App() {
const [dish, setDish] = useState(null);
useEffect(() => {
// TypeScript'da: setDish((await fetchDish()) as Dish)
fetchDish().then(setDish);
}, []);
if (dish === null) return <p>Yuklanmoqda…</p>;
return (
<p>
{dish.name}: jami {dish.price + DELIVERY} so'm
</p>
);
}Ekranda:
Osh: jami 350005000 so'm"35000" + 5000 — satr ulash (Yashirin coercion darsidagi + tuzog'i). Hech qanday xato, ogohlantirish yo'q — faqat noto'g'ri raqam. Eng xavfli xato turi: jim, ishonarli ko'rinadi va pul bilan bog'liq.
3.2 Chegarada tekshirish
Davosi — javobni chegarada (API qatlamida) ish vaqtida tekshirish. Qo'lda yozish mumkin (typeof price === "number") — vazifalar 15-qismdan beri shunday qilardi (serverRoyxatiniTekshir, 50 qatorga yaqin). Lekin har yangi maydon — yangi if, tur esa alohida yoziladi va tekshiruv bilan ajralib ketishi mumkin.
Zod bu ikkisini bitta qiladi: sxema ish vaqtida tekshiradi va TypeScript turini beradi. Natija oynasida zod/mini — vazifalar kanonidagi variant (nega "mini" — «Narxi: hajm, CSP va jitless» bo'limida):
import { z } from "zod/mini";
// sababi — «Narxi» bo'limida
z.config({ jitless: true });
const dishSchema = z.object({
name: z.string().check(z.minLength(1, "nomi bo'sh")),
price: z.number("narx son emas").check(
z.int("narx butun emas"),
z.positive("narx musbat emas"),
),
});
const good = dishSchema.safeParse({ name: "Osh", price: 35000 });
console.log(good.success, good.data);
const bad = dishSchema.safeParse({ name: "Osh", price: "35000" });
console.log(bad.success);
const [issue] = bad.error.issues;
console.log(issue.path.join("."), "—", issue.message);
const extra = dishSchema.parse({
name: "Manti",
price: 30000,
admin: true,
});
console.log(extra);
export default function App() {
return <p>Natija konsolda</p>;
}Konsolda:
true {name: 'Osh', price: 35000}
false
price — narx son emas
{name: 'Manti', price: 30000}To'rt qator — uch saboq:
- To'g'ri javob —
success: truevadata— tekshirilgan obyekt. - Satr narx —
success: false,issues[0]da yo'l (price) va xabar. "Qayerda buzuq" — aniq. - Oxirgi qator: ortiqcha maydon (
admin: true) — tashlab yuborildi.z.objectfaqat sxemadagi kalitlarni qoldiradi — oq ro'yxat. Bu «Hujumchi nigohi» da muhim.
safeParse — xato tashlamaydi, natija obyekt qaytaradi. parse — mos kelmasa ZodError tashlaydi. Zod natija oynasida xabarlarni faqat inglizcha beradi, shuning uchun o'zbekcha xabarni sxemada o'zimiz yozdik ("narx son emas"). Zod chuqurroq — Zod: coerce, refine, transform darsida.
Tekshirib ko'ring: Sxemada
price: z.number(). Server"price": "35000"yuborsa, negaz.coerce.number()(satrni songa aylantirish) bilan "tuzatib" qo'ya qolmaymiz?
Javob
Chunki bu — kontrakt buzilishi, va uni yashirish keyin qimmatga tushadi. Bugun "35000", ertaga "35 000" yoki "35000.00 UZS" keladi — coerce NaN beradi yoki jimgina noto'g'ri son. Server javobida xato ko'rinsa, uni e'lon qilish kerak: frontend aniq xabar beradi, backend jamoasi tuzatadi. coerce URL va forma kabi "hamma narsa satr" bo'lgan joylar uchun (TanStack Router darsidagi ?sahifa=).
4. shartnoma() — har metod tekshirilgan tur qaytaradi
4.1 Bitta yordamchi
API qatlamidagi har metod uchun bitta qoida: so'rov → sxema → tekshirilgan qiymat yoki xato. Uni bitta funksiyaga yig'amiz. Natija oynasida vazifalar ning soddalashtirilgan nusxasi:
import { useEffect, useState } from "react";
import { z } from "zod/mini";
z.config({ jitless: true });
class ApiError extends Error {
constructor(message, { code, cause }) {
super(message, { cause });
this.name = "ApiError";
this.code = code;
}
}
const dishSchema = z.object({
id: z.int().check(z.positive("id musbat emas")),
name: z.string().check(z.minLength(1, "nomi bo'sh")),
price: z.number("narx son emas"),
});
const menuSchema = z.array(dishSchema);
// Javob shartnomaga mos kelmasa — bitta turdagi xato
function contract(schema, data) {
const result = schema.safeParse(data);
if (!result.success) {
const [issue] = result.error.issues;
const where = issue.path.join(".");
throw new ApiError(
`Server javobi kutilgan shaklda emas (${where}: ` +
`${issue.message}).`,
{ code: "JAVOB", cause: result.error },
);
}
return result.data;
}
// "Server" — ikkinchi taom buzuq
const serverJson = () =>
new Promise((resolve) =>
setTimeout(
() =>
resolve([
{ id: 1, name: "Osh", price: 35000 },
{ id: 2, name: "Manti", price: "30000" },
]),
200,
),
);
const api = {
menu: async () => contract(menuSchema, await serverJson()),
};
export default function App() {
const [state, setState] = useState({ status: "loading" });
useEffect(() => {
api
.menu()
.then((menu) => setState({ status: "ok", menu }))
.catch((error) => {
console.warn(error.code, error.message);
setState({ status: "error", error });
});
}, []);
if (state.status === "loading") return <p>Yuklanmoqda…</p>;
if (state.status === "error") {
return <p role="alert">Menyuni ochib bo'lmadi.</p>;
}
return <p>{state.menu.map((d) => d.name).join(", ")}</p>;
}Konsolda:
JAVOB Server javobi kutilgan shaklda emas (1.price: narx son emas).Ekranda:
Menyuni ochib bo'lmadi.Yo'l 1.price — massivning ikkinchi elementi (indeks 1), price maydoni. Dasturchi konsolda aniq joyni ko'radi; foydalanuvchi esa tushunarli xabar oladi, 350 million so'mlik hisob emas. cause da asl Zod xatosi saqlanadi — Sentry kabi xato kuzatish xizmatiga to'liq ma'lumot ketadi.
Muhimi — komponent sxemani bilmaydi. U api.menu() dan yo tekshirilgan massiv, yo ApiError oladi. Tekshirishni unutib bo'lmaydi: tekshirilmagan qiymatni olishning boshqa yo'li yo'q.
4.2 Xato normallashtirish: bitta tur, bir nechta kod
ApiError ning code maydoni — UI uchun asosiy narsa. vazifalar dagi kodlar (turlar.ts, kanon):
| Kod | Qachon | UI nima qiladi |
|---|---|---|
TARMOQ |
internet yo'q, DNS, CORS | "Serverga ulanib bo'lmadi", keshdagini ko'rsatadi |
VAQT |
8 s ichida javob yo'q | o'sha, "qayta urinish" |
BEKOR |
AbortController — foydalanuvchi ketdi |
hech narsa (xato emas) |
TOPILMADI |
404 | "topilmadi" sahifasi |
NOTOGRI |
400, 422 | forma xatolari |
SERVER |
5xx | "serverda muammo, keyinroq" |
JAVOB |
javob JSON emas yoki shartnomaga mos emas | "saqlanmadi", konsolda maydon |
Bu xato normallashtirish (error normalization): fetch ning TypeError i, HTTP status, JSON xatosi va Zod xatosi — hammasi bitta ApiXatosi turiga keltiriladi. Komponent if (xato.kod === "BEKOR") return; deb yozadi, instanceof TypeError va res.status ni bilmaydi. Data mode darsidagi loader aynan BEKOR ni ajratgan edi.
Maslahat:
401(kirmagan) ko'pincha alohida kod oladi: API qatlami uni ko'rib, foydalanuvchini kirish sahifasiga yuboradi (himoyalangan marshrutlar). Bitta joyda — har sahifada emas.vazifalarda haqiqiy auth yo'q, shuning uchun bu kod hali yo'q — 28-qismda.
5. Backend bilan kontrakt: OpenAPI va kod generatsiyasi
5.1 Sxemani kim yozadi?
Hozir sxemani frontend yozdi — backend nima yuborishini "bilib". Backend o'zgarsa, frontend sxemasi eskiradi, va biz buni faqat ish vaqtida, JAVOB xatosi bilan bilamiz. Yaxshiroq yo'l — kontraktni bitta joyda yozish va ikkala tomonni undan olish.
OpenAPI — HTTP API'ni tavsiflaydigan standart: qaysi manzillar bor, qanday parametrlar, javob qanday shaklda (JSON yoki YAML fayl). Ko'p backend frameworklari uni koddan o'zi chiqaradi (NestJS, FastAPI — 25–30-qismlarda). Fayldan esa frontend uchun kod yasaladi:
Vosita (2026-10-14, npm view) |
Nima yasaydi |
|---|---|
| openapi-typescript 7.13.0 (2026-02-11) | faqat turlar (paths["/vazifalar"]["get"]…); juftligi — openapi-fetch 0.17.0 kichik klienti |
| orval 8.42.0 (2026-10-13) | turlar + tayyor klient funksiyalari, ixtiyoriy TanStack Query hook'lari va Zod sxemalari |
Hey API (@hey-api/openapi-ts) 0.99.0 (2026-06-22) |
turlar, SDK, plaginlar orqali Zod va TanStack Query |
Versiyalar faqat yo'nalish uchun: Hey API hali 0.x — kichik versiyalar orasida ham API o'zgarishi mumkin; orval'ning oxirgi relizi bir kunlik — kurs qoidasiga ko'ra (7 kun) o'rnatishda oldingisini oling.
npx openapi-typescript ./openapi.yaml -o src/api/schema.d.tsNatija — har endpoint uchun avtomatik turlar. Backend price ni satrga o'zgartirsa, faylni qayta generatsiya qilasiz va tsc frontend'dagi har bir price + 5000 ni qizil qiladi — xato build paytida, foydalanuvchidan oldin.
Diqqat: Generatsiya qilingan turlar ham
asga o'xshaydi — ular kontraktni aytadi, lekin ish vaqtida tekshirmaydi. Server hujjatga zid javob bersa (xato, eski versiya), turlar yordam bermaydi. Shuning uchun generatorlar sxema (Zod) ham yasashni taklif qiladi: tur — kompilyatsiya uchun, sxema — ish vaqti uchun. Bu vositalar kanonda o'lchanmagan; «Bahor» API'sida OpenAPI fayli yo'q.
5.2 To'liq TypeScript stekida — tRPC
Backend ham TypeScript bo'lsa va bitta repoda tursa, OpenAPI'siz ham bo'ladi: backend funksiyasining turi to'g'ridan frontend'ga o'tadi. Bu — tRPC g'oyasi (GraphQL va tRPC darsida tanishuv). vazifalar — oddiy REST, backend boshqa loyiha, shuning uchun bizning yo'l — qo'lda yozilgan Zod sxemasi.
6. Vazifalar qadami: Zod kontrakti (holat 12)
6.1 Nima o'zgaradi
git switch -c feat/api-shartnoma
npm i --save-exact zod@4.6.5- Yangi fayl
api-sxema.ts— vazifa va ro'yxat sxemalari, xato matni. api.ts—shartnoma()va har metod tekshirilgan tur qaytaradi.paket.tsdanserverRoyxatiniTekshirvaserverVazifasiniTekshir(15-qismdagi qo'lda yozilgan 50 qator) o'chiriladi, chaqiruvchilar soddalashadi.
6.2 api-sxema.ts
Kanondan aynan (izohlar telefon uchun qayta bo'lingan):
// "zod/mini" — funksional API (.check(...)), klassik "zod" emas:
// o'lchandi (natijalar/12-zod-hajm.txt) — klassik +24.2 KB gzip,
// mini +5.8 KB; qoidalar va xabarlar bir xil
import { z } from "zod/mini";
import { Vazifa } from "./vazifa.ts";
// CSP (13/#32): script-src 'self' — eval yo'q. Zod 4 obyekt
// sxemalarini tezlashtirish uchun "new Function" bor-yo'qligini SINAB
// ko'radi; xato ushlansa ham brauzer buni CSP buzilishi deb e'lon
// qiladi (o'lchandi: natijalar/12-zod-csp.txt). jitless — sinovsiz
z.config({ jitless: true });
/** Bitta vazifa: faqat uch maydon, qolgani tashlanadi (oq ro'yxat) */
export const vazifaSxemasi = z.object({
id: z.int().check(z.positive("id musbat son emas")),
matn: z.string().check(
z.refine((matn) => matn.trim() !== "", "matn bo'sh"),
z.refine(
(matn) => matn.length <= Vazifa.MAKS_UZUNLIK,
"matn juda uzun",
),
),
bajarildi: z.boolean(),
});
/** GET va PUT javobi: versiya — server hisobi (0 — kalit
* yangi) */
export const serverRoyxatiSxemasi = z.object({
versiya: z.int().check(z.nonnegative()),
vazifalar: z
.array(vazifaSxemasi)
.check(
z.refine(
(vazifalar) =>
new Set(vazifalar.map((v) => v.id)).size === vazifalar.length,
"id takrorlangan",
),
),
});
export type ServerRoyxati = z.infer<typeof serverRoyxatiSxemasi>;Vazifa.MAKS_UZUNLIK— 17-qismdagi o'sha qoida: sxema domen qoidasini qayta yozmaydi, undan oladi.z.refine— maxsus shart: "matn bo'sh emas", "id takrorlanmaydi". Bu ikkinchisi — butun massiv ustidagi tekshiruv, bitta maydonda emas.z.infer<typeof …>— tur sxemadan.ServerRoyxatini qo'lda yozish shart emas; sxema o'zgarsa, tur ham o'zgaradi.- Xato matni —
sxemaXatosi(): birinchi muammoning yo'li va xabari, «Bitta yordamchi» bo'limidagidek ("vazifalar.0.id: id musbat son emas").
6.3 api.ts — shartnoma va metodlar
/**
* Javob shartnomaga mos kelmasa — ApiXatosi (kod JAVOB): ekranga
* "Saqlanmadi: ..." bo'lib chiqadi, Zod xatosi cause'da
*/
function shartnoma<T>(sxema: z.ZodMiniType<T>, malumot: unknown): T {
const natija = sxema.safeParse(malumot);
if (!natija.success) {
throw new ApiXatosi(
`Server javobi kutilgan shaklda emas (${sxemaXatosi(natija.error)}).`,
{ kod: "JAVOB", cause: natija.error },
);
}
return natija.data;
}
// … apiKlient ichida, har metod — so'rov, keyin shartnoma:
royxat: async (sozlama?: SorovSozlamasi): Promise<ServerRoyxati> =>
shartnoma(serverRoyxatiSxemasi, await yubor(manzil, sozlama)),
// DELETE javobi bo'sh (204) — tekshiradigan narsa yo'q
ochir: async (
id: number,
sozlama?: SorovSozlamasi,
): Promise<void> => {
await yoz(`/${id}`, "DELETE", undefined, sozlama);
},shartnoma<T> — generic: sxemaning turidan natija turi chiqadi. Oldin metodlar Promise<unknown> qaytarardi va chaqiruvchi tekshirishi kerak edi — loader, action, server-sinxron.ts, App.tsx. Endi tekshiruv bitta joyda, chaqiruvchilarda bitta qator qisqardi (kanon diff):
- const javob = serverRoyxatiniTekshir(await api.royxat({ signal }));
+ const javob = await api.royxat({ signal });6.4 Narxi: hajm, CSP va jitless
Hajm. Bir xil sxema uch variantda qurildi (natijalar/12-zod-hajm.txt va tajriba/hajm.txt):
- zod 4.6.5 (klassik)ilovada24,2 KB
- zod/mini 4.6.5 (kanon)5,8 KB
- Valibot 1.5.0faqat sxema1,6 KB
Manba: O'lchandi: vite build, gzip -9, natijalar/12-zod-hajm.txt (holat 10: 122 503 B, klassik 146 721 B, mini 128 327 B) va natijalar/tajriba/hajm.txt (sxema-valibot 1 648 B), 2026-10-13
Klassik zod — zanjirli metodlar (z.string().min(1).max(100)), hamma narsa bitta obyektda, shuning uchun Vite foydalanilmagan qismlarni tashlab yubora olmaydi. zod/mini — funksiyalar (z.string().check(z.minLength(1))): ishlatilmagani to'plamga kirmaydi. Qoidalar va natija bir xil — 4 barobar kichik. Valibot yana ham kichik (1.6 KB) — u forma kutubxonalari muqobillari darsida; Zod — kurs yo'nalishi (15-qismdan beri).
CSP. vazifalar qattiq CSP ostida ishlaydi (script-src 'self', Trusted Types — Brauzer xavfsizlik modeli). Zod 4 obyekt sxemalarini tezlashtirish uchun new Function ishlay oladimi — sinab ko'radi. Kanon Chrome 155 da o'lchadi (natijalar/12-zod-csp.txt, aynan):
C:/Users/VICTUS/AppData/Local/Temp/kanon19/dist-zod-jit
CSP buzilishlari: ["require-trusted-types-for trusted-types-sink index-tzfR33cv.js"]
konsol xatolari: ["This document requires 'TrustedScript' assignment. The action has been blocked."]
../vazifalar-v9/dist
CSP buzilishlari: []
konsol xatolari: []Birinchisi — jitless siz: Zod xatoni o'zi ushlab, sekinroq yo'lga o'tdi — ilova ishladi. Lekin brauzer CSP buzilishini qayd etdi va konsolda qizil xato chiqdi: "Bu hujjat 'TrustedScript' tayinlashni talab qiladi. Amal bloklandi." CSP hisobotlari yig'ilsa, har sahifa ochilishi — "hujum" xabari. Ikkinchisi — z.config({ jitless: true }) bilan: Zod sinamaydi, toza. Tezlik farqi bizning 100 ta vazifada sezilmaydi.
6.5 Tekshiruvlar
git add -A
git commitCommit (kanon): feat: API javoblari — Zod sxemasi (zod/mini, jitless), tiplangan metodlar.
npm test— node:test 182 (yangiapi-sxema.test.js: to'g'ri javob — faqat uch maydon, buzuq id, takror, bo'sh matn;paket.test.jsdagi server testlari shu yerga ko'chdi). Vitest 56.- Brauzer: 159/159, 72/72, 19-qism — 63/63. Yangilari (aynan):
✅ buzuq ro'yxat javobi: aloqa xato, ro'yxat — keshdan: ["Serverga ulanib bo'lmadi. Ro'yxat — shu brauzerdagi nusxa.",3]
✅ konsol: qaysi maydon buzuqligi aytiladi: true
✅ buzuq PATCH javobi: «Saqlanmadi», katak qaytdi: ["Saqlanmadi: Server javobi kutilgan shaklda emas (matn: matn bo'sh).",false]
✅ Zod CSP ostida: buzilish yo'q (jitless): []Birinchisi — server buzuq ro'yxat yubordi: ilova yiqilmadi, brauzerdagi nusxani ko'rsatdi. Uchinchisi — PATCH javobida matn bo'sh: "Saqlanmadi", katakcha oldingi holatiga qaytdi (optimistik o'zgarish bekor). Hajm: gzip 122.5 → 128.3 KB (+5.8). Diff: 11 fayl, +250 −170.
Maslahat: Import fayli (
paket.ts) hamon o'z qo'lda yozilgan tekshiruvida — u ham Zod'ga ko'chishi mumkin edi. Kanon buni ataylab qoldirdi (TEXNIK-QARZ): bitta qadamda bitta o'zgarish. Haqiqiy loyihalarda ham refaktor bo'laklarga bo'linadi.
7. Hujumchi nigohi
Server — ishonchli manba emas. Uning javobi ham foydalanuvchi kiritgan ma'lumot kabi tekshiriladi. Nega?
- Server buzilgan bo'lishi mumkin. Hujumchi bazaga yozib qo'ydi: vazifa matni —
<img src=x onerror=…>yoki 10 MB matn. React matnni escape qiladi, lekin uzunlik cheklovi (matn juda uzun) ekran va xotirani himoya qiladi. - Ortiqcha maydonlar —
{ "id": 1, "matn": "…", "admin": true, "__proto__": {…} }.z.objectsxemada yo'q kalitlarni tashlaydi («Chegarada tekshirish» dagi uchinchi qator):adminilova ichiga kirmaydi. Agar javobni{ ...javob }bilan to'g'ridan holatga yoysangiz, hujumchi kutilmagan maydonlarni qo'shishi mumkin edi. - O'rtadagi odam (MITM) yoki eski kesh. Service Worker yoki proksi eski formatdagi javobni qaytardi. Sxema buni
JAVOBxatosi bilan ushlaydi va ilova yiqilmaydi —vazifalarda keshdagi nusxaga qaytadi. - Teskari yo'nalish ham bor: frontend tekshiruvi serverni himoya qilmaydi. Server kelgan so'rovni o'zi tekshiradi — o'sha Zod sxemasi bilan, agar backend ham TypeScript bo'lsa (25-qism).
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
350005000 so'm — satr ulandi |
javob as bilan tiplangan, tekshirilmagan |
sxema bilan safeParse |
fetch o'nta komponentda |
API qatlami yo'q | api.ts — yagona eshik |
| 500 javobi "muvaffaqiyat" | res.ok tekshirilmagan |
qatlamda status → xato kodi |
| xatolar har xil turda | normallashtirish yo'q | bitta ApiXatosi, kod maydoni |
| "Server javobi noto'g'ri" — qaysi maydon? | Zod xatosining yo'li tashlangan | issues[0].path.join(".") |
CSP: trusted-types-sink, konsolda TrustedScript |
Zod JIT sinovi | z.config({ jitless: true }) |
| to'plam +24 KB | klassik zod |
zod/mini |
| tur va tekshiruv ajralib ketdi | tur qo'lda, tekshiruv qo'lda | z.infer<typeof sxema> |
9. Mashqlar
Mashqlar kurs/mashqlar/19/12-api-shartnoma/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi kod?
vazifalar dagi ApiXatosi kodini yozing:
- Server 503 qaytardi →
- Javob keldi, lekin
vazifalarmaydoni massiv emas → - Foydalanuvchi sahifadan ketdi, router
request.signalni bekor qildi → - Wi-Fi o'chgan →
Yechim
SERVER (5xx), JAVOB (shartnoma buzildi — status 200 bo'lsa ham), BEKOR (xato emas, UI hech narsa ko'rsatmaydi), TARMOQ (fetch TypeError tashladi). Ikkinchisiga e'tibor bering: HTTP nuqtai nazaridan hammasi yaxshi (200), lekin kontrakt bo'yicha — xato. Sxema bo'lmaganda bu holat umuman xato bo'lmasdi — komponent undefined.map da yiqilardi.
2-mashq (o'rta): buyurtma sxemasi
«Bahor» buyurtma javobi: { id, items: [{ name, qty }], total }. Sxema yozing: id — musbat butun son, items — kamida bitta element, qty — musbat butun son, total — manfiy bo'lmagan son. Xabarlar o'zbekcha. Quyidagi uchta javobni tekshirib, har biriga ok yoki yo'l — xabar chiqaring.
Ishora: «Chegarada tekshirish» bo'limi; massiv uzunligi — z.minLength(1, "…").
import { z } from "zod/mini";
z.config({ jitless: true });
const responses = [
{ id: 7, items: [{ name: "Osh", qty: 2 }], total: 70000 },
{ id: 8, items: [], total: 0 },
{ id: 9, items: [{ name: "Manti", qty: 0 }], total: 30000 },
];
// orderSchema va tekshiruv — shu yergaYechim
import { z } from "zod/mini";
z.config({ jitless: true });
const orderSchema = z.object({
id: z.int().check(z.positive("id musbat emas")),
items: z
.array(
z.object({
name: z.string(),
qty: z.int().check(z.positive("soni musbat emas")),
}),
)
.check(z.minLength(1, "buyurtma bo'sh")),
total: z.number().check(z.nonnegative("jami manfiy")),
});
const responses = [
{ id: 7, items: [{ name: "Osh", qty: 2 }], total: 70000 },
{ id: 8, items: [], total: 0 },
{ id: 9, items: [{ name: "Manti", qty: 0 }], total: 30000 },
];
for (const r of responses) {
const result = orderSchema.safeParse(r);
if (result.success) {
console.log(r.id, "ok");
} else {
const [issue] = result.error.issues;
console.log(r.id, issue.path.join("."), "—", issue.message);
}
}
export default function App() {
return <p>Natija konsolda</p>;
}Konsolda:
7 ok
8 items — buyurtma bo'sh
9 items.0.qty — soni musbat emas8-buyurtmada xato butun massivda (items), 9-da — ichma-ich yo'l (items.0.qty): massivning birinchi elementi, qty maydoni. Yo'l qanchalik chuqur bo'lmasin, Zod uni to'liq beradi.
3-mashq (qiyin): API qatlami bilan sahifa
api.order(id) metodini yozing: soxta server (fakeServer(id), tayyor) javobini contract() bilan tekshirsin. Komponent ikki buyurtmani ko'rsatsin: 7 (to'g'ri) va 9 (buzuq). Buzuq bo'lsa — Buyurtma #9: ochib bo'lmadi, konsolda JAVOB va xabar. Ishora: «Bitta yordamchi» bo'limidagi contract va ApiError; 2-mashqdagi sxemaning qisqa varianti tayyor.
Yechim
import { useEffect, useState } from "react";
import { z } from "zod/mini";
z.config({ jitless: true });
class ApiError extends Error {
constructor(message, { code, cause }) {
super(message, { cause });
this.name = "ApiError";
this.code = code;
}
}
const orderSchema = z.object({
id: z.int(),
total: z.number("jami son emas"),
});
function contract(schema, data) {
const result = schema.safeParse(data);
if (!result.success) {
const [issue] = result.error.issues;
throw new ApiError(
`Javob noto'g'ri (${issue.path.join(".")}: ${issue.message})`,
{ code: "JAVOB", cause: result.error },
);
}
return result.data;
}
const DATA = { 7: { id: 7, total: 70000 }, 9: { id: 9, total: "?" } };
const fakeServer = (id) =>
new Promise((resolve) => setTimeout(() => resolve(DATA[id]), 200));
const api = {
order: async (id) => contract(orderSchema, await fakeServer(id)),
};
function Order({ id }) {
const [text, setText] = useState("…");
useEffect(() => {
api
.order(id)
.then((o) => setText(`${o.total} so'm`))
.catch((error) => {
console.warn(error.code, error.message);
setText("ochib bo'lmadi");
});
}, [id]);
return (
<p>
Buyurtma #{id}: {text}
</p>
);
}
export default function App() {
return (
<>
<Order id={7} />
<Order id={9} />
</>
);
}Konsolda:
JAVOB Javob noto'g'ri (total: jami son emas)Ekranda:
Buyurtma #7: 70000 so'm
Buyurtma #9: ochib bo'lmadiBitta sahifada bir buyurtma ishladi, ikkinchisi xato — ilova yiqilmadi, faqat bitta qator "ochib bo'lmadi". Order sxemani ham, fetch ni ham bilmaydi: faqat api.order(id). Keyingi darslarda TanStack Query aynan shunday api metodini queryFn sifatida oladi.
4-mashq: Vazifalar qadami
feat/api-shartnoma branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: zod@4.6.5, api-sxema.ts, api.ts dagi shartnoma va metod turlari, paket.ts dan server tekshiruvlarini olib tashlash, testlar. Keyin commit.
Yechim
Tekshiruv ro'yxati:
npm run tip— toza;api.royxat()ning turiPromise<ServerRoyxati>(VS Code'da sichqonchani ustiga olib boring).grep -r "serverRoyxatiniTekshir" src— bo'sh.- DevTools → Network → javobni "Override" qilib, bitta vazifaning
matnini""qiling: ro'yxat "Serverga ulanib bo'lmadi…", konsoldavazifalar.N.matn: matn bo'sh. - DevTools → Console — CSP yoki
TrustedScriptxatosi yo'q. npm test— node 182, Vitest 56; build — gzip ~128 KB.
10. Real ishda
- Har jiddiy frontend — API qatlami (
api/,services/) va xato normallashtirish. Intervyuda "API xatolarini qanday boshqarasiz?" — klassik savol. - Zod chegarada — Next.js, Remix, tRPC loyihalarida server javobi va forma ma'lumoti Zod bilan tekshiriladi. "Parse, don't validate" — tekshirilgan qiymatni turi bilan qaytarish g'oyasi.
- OpenAPI kodgen — katta jamoalarda (backend alohida) orval yoki Hey API bilan klient va hook'lar avtomatik yasaladi; CI'da "OpenAPI o'zgardi — frontend build yiqildi" — normal holat.
- Xato kuzatish —
causedagi Zod xatosi Sentry'ga ketadi: "1.price — narx son emas" degan xabar backend jamoasiga darhol yo'l ko'rsatadi.
Xulosa
- API qatlami — frontend va backend orasidagi yagona eshik: manzil, vaqt chegarasi, bekor qilish, xatolar va tekshiruv bitta joyda.
- TypeScript turi (
as) — va'da; server javobi ish vaqtida sxema bilan tekshiriladi (safeParse). shartnoma(sxema, javob)— mos kelsa tiplangan qiymat, aks holdaApiXatosi(JAVOB) yo'l bilan: "vazifalar.0.id: …".- Xatolar bitta turga normallashtiriladi:
TARMOQ,VAQT,BEKOR,TOPILMADI,SERVER,JAVOB. z.objectortiqcha maydonlarni tashlaydi (oq ro'yxat);z.infer— tur sxemadan.- Kanon:
zod/mini(+5.8 KB, klassik +24.2),jitless(CSP toza). OpenAPI bo'lsa — openapi-typescript, orval, Hey API.
Keyingi dars: TanStack Query I: useQuery asoslari — vazifalar ning server state'i bitta keshga ko'chadi: useQuery, queryKey, queryFn va yuklanish holatlari.
Manbalar
- Zod 4: Basics, Zod Mini,
safeParse,z.config({ jitless })— zod.dev (v4.6.5) - OpenAPI Specification — spec.openapis.org; openapi-typescript — openapi-ts.dev; orval — orval.dev; Hey API — heyapi.dev
- MDN: Content Security Policy, Trusted Types,
Error: cause— developer.mozilla.org - Alexis King: "Parse, don't validate" (2019) — lexi-lambda.github.io
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!