Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Uch daraja — bitta so'rov
- 2.1 Raw SQL — pg
- 2.2 Query builder — Kysely
- 2.3 ORM — Prisma
- 2.4 Bazaga nima boradi
- 3. Query builder ichidan
- 4. Ikki ORM naqshi
- 4.1 Active Record
- 4.2 Data Mapper
- 5. Leaky abstraction: kanondagi uchta teshik
- 5.1 TypeORM va Sequelize: yo'q ustun
- 5.2 Prisma: contains va %
- 5.3 Prisma: matn bo'yicha saralash
- 6. Tezlik: o'lchangan raqamlar
- 6.1 Oltita kutubxona
- 6.2 Prisma va pg — bir xil ombor
- 7. SQL bilmay ORM ishlatish xavfi
- 8. Qachon qaysi biri
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qaysi daraja
- 2-mashq (o'rta): builder'ga orderBy
- 3-mashq (qiyin): bo'sh qiymatga chidamli butun son
- 11. Loyiha qadami: holat 19 — eski teshiklarni yopish
- 11.1 PORT= — jim 0
- 11.2 CORS: oxiridagi /
- 11.3 Pool xatosi jim yutilardi
- 11.4 Flaky testlar
- 11.5 Tekshiruv va commit
- 12. Real ishda
- Xulosa
- Manbalar
ORM, query builder va raw SQL: uch abstraksiya darajasi va to'g'ri tanlov
Qisqacha: Ma'lumotlar bazasi bilan uch darajada ishlash mumkin. Raw SQL (
pg) — SQL'ni o'zingiz yozasiz: to'liq nazorat, lekin tiplar va xatolar sizda. Query builder (Kysely, Knex) — SQL'ni JS metodlari bilan yig'asiz: tipli, lekin SQL'ga yaqin. ORM (Prisma, TypeORM) — jadval o'rniga modellar bilan ishlaysiz: eng qulay, lekin SQL ko'rinmaydi va ba'zan "teshik" ochiladi. Kanon o'lchovida bitta so'rovdagi farq millisekundlar ichida (0.1–7 ms) — tanlov mezoni tezlik emas, tip sifati, migratsiya va SQL ustidan nazorat.
Bu darsda
- Bir xil so'rovni raw SQL, query builder va ORM bilan yozib, farqini ko'rasiz.
- Active Record va Data Mapper naqshlarini ajratasiz.
- "Leaky abstraction" nima ekanini kanondagi uchta haqiqiy xato misolida tushuntirasiz.
- SQL bilmay ORM ishlatishning xavfini bilasiz.
vazifalar-apining 25–26-qismlardan qolgan uchta teshigini yopasiz (holat 19).
Oldin bilishingiz kerak: node-postgres (pg) asoslari, Parametrli so'rovlar va SQL injection, N+1 muammosi, Managed va serverless Postgres.
1. Nega bu kerak?
26-qismda vazifalar-api ning PgOmbori sini qo'lda yozdik: har so'rov — SQL satri, har natija — rows[0], har tur — qo'lda (id string bo'lib kelgani esingizdami?). Ishladi, tez ham. Lekin bir necha muammo qoldi:
- SQL matnida jadval nomi xato bo'lsa, buni faqat ishga tushganda bilasiz — TypeScript yordam bermaydi.
rowsning turianyga yaqin: ustun nomini noto'g'ri yozsangiz, kompilyator jim.- Sxema o'zgarsa, qaysi so'rovlar buzilishini qo'lda qidirasiz.
Bu muammolarni hal qilish uchun ikki qatlam o'ylab topilgan. Ular SQL'ni yashiradi yoki tipli qiladi — lekin har yashirishning narxi bor:
- Query builder (so'rov yig'uvchi) — SQL'ni JS metodlari zanjiri bilan yig'adigan kutubxona:
.selectFrom("vazifalar").where(…). SQL tuzilishi saqlanadi, faqat matn o'rniga metodlar. - ORM (Object-Relational Mapping) — jadval qatorlarini JS obyektlariga "xaritalaydigan" kutubxona: siz
vazifamodeli bilan ishlaysiz, SQL'ni u yozadi. - Raw SQL ("xom" SQL) — SQL matnini o'zingiz yozib,
pgbilan yuborish; 26-qismda shunday qildik.
O'xshatish: Toshkentdan Samarqandga borish. Raw SQL — o'zingiz haydaysiz: har burilishni bilasiz, lekin charchaysiz. Query builder — navigator bilan haydaysiz: yo'lni u ko'rsatadi, rul sizda. ORM — taksi: manzilni aytasiz, qaysi yo'ldan ketishini haydovchi hal qiladi. Ko'pincha qulay, lekin ba'zan aylanma yo'ldan ketadi — va siz buni faqat hisobni ko'rib bilasiz.
Diqqat: O'lchovlar — kanondan (
kattabazasi, 500 000 vazifa, PostgreSQL 18.6, Node 24.21.0, 2026-10-25); mashina parallel ishlar bilan yuklangan bo'lishi mumkin, raqamlar nisbat sifatida o'qilsin. Brauzerda ishlaydigan JS misollar va holat 19 kodi — Node 24 da tekshirilgan.
2. Uch daraja — bitta so'rov
Savol: "77-foydalanuvchining o'chirilmagan vazifalari, tartib bo'yicha, birinchi 50 tasi". Kanon uni oltita kutubxona bilan yozdi; uchtasini ko'ramiz.
2.1 Raw SQL — pg
const { rows } = await pool.query(
`SELECT id, matn, tartib FROM vazifalar
WHERE egasi_id = $1 AND ochirilgan_vaqt IS NULL
ORDER BY tartib, id LIMIT 50`,
[77],
);Nima yozsangiz, bazaga aynan shu boradi. Parametr ($1) — Parametrli so'rovlar darsidagidek. Natijaning birinchi qatori kanonda: {"id":"709","matn":"Kitob o'qish 709 #uy","tartib":709} — id string (bigint JS'ga sig'masligi mumkin, pg uni satr qiladi).
2.2 Query builder — Kysely
const rows = await db
.selectFrom("vazifalar")
.select(["id", "matn", "tartib"])
.where("egasi_id", "=", 77)
.where("ochirilgan_vaqt", "is", null)
.orderBy("tartib").orderBy("id")
.limit(50)
.execute();Har metod SQL'ning bir qismiga mos keladi: selectFrom → FROM, where → WHERE. Farqi — tiplar: Database interfeysida vazifalar jadvali va uning ustunlari e'lon qilingan, shuning uchun "egasi_idd" deb yozsangiz, TypeScript darhol xato beradi. Natija turi ham avtomatik: { id, matn, tartib }[].
2.3 ORM — Prisma
const rows = await db.vazifa.findMany({
where: { egasiId: 77n, ochirilganVaqt: null },
orderBy: [{ tartib: "asc" }, { id: "asc" }],
select: { id: true, matn: true, tartib: true },
take: 50,
});Bu yerda jadval va ustun yo'q — model (vazifa) va maydonlar (egasiId). Bazada ular vazifalar va egasi_id ekanini Prisma sxema fayli biladi (Prisma: modellar va atributlar). 77n — BigInt literal: Prisma bigint ustunni JS bigint qilib qaytaradi va xuddi shunday kutadi.
2.4 Bazaga nima boradi
Uchala kod ham oxir-oqibat SQL. Biz Prisma 7.10.1 ni o'z PostgreSQL 17.6 klasterimizda holat 20 bazasi bilan ishga tushirdik va u yuborgan SQL'ni log qildik (shunga o'xshash so'rov, findFirst):
SQL: SELECT "public"."vazifalar"."id", "public"."vazifalar"."matn", "public"."vazifalar"."tartib" FROM "public"."vazifalar" WHERE "public"."vazifalar"."egasi_id" = $1 ORDER BY "public"."vazifalar"."tartib" ASC LIMIT $2 OFFSET $3Prisma ham parametrlar bilan, to'liq nomlar bilan, toza SQL yubordi. ORM sehr emas — u SQL generatori. Uni yaxshi ishlatish uchun SQL'ni bilish kerak, aks holda nima yuborilayotganini baholay olmaysiz.
flowchart TD
A[ORM: Prisma, TypeORM] -->|SQL yasaydi| D[(PostgreSQL)]
B[Query builder: Kysely, Knex] -->|SQL yig'adi| D
C[Raw SQL: pg] -->|SQL yuboradi| DDiagrammaga qarang: uchala yo'l ham bitta bazaga, bitta tilda — SQL'da — gapiradi. Farq — SQL'ni kim yozadi: siz, siz va builder birga, yoki kutubxona.
3. Query builder ichidan
Query builder qanday ishlashini ko'rish uchun eng kichigini o'zimiz yozamiz. U SQL bajarmaydi — faqat SQL matni va parametrlarni yig'adi:
function selectFrom(jadval) {
const holat = { ustunlar: ["*"], shartlar: [], qiymatlar: [] };
const q = {
select(ustunlar) { holat.ustunlar = ustunlar; return q; },
where(ustun, op, qiymat) {
holat.qiymatlar.push(qiymat);
const n = holat.qiymatlar.length;
holat.shartlar.push(`${ustun} ${op} $${n}`);
return q;
},
limit(n) { holat.limit = n; return q; },
compile() {
let sql = `SELECT ${holat.ustunlar.join(", ")} FROM ${jadval}`;
if (holat.shartlar.length > 0) {
sql += ` WHERE ${holat.shartlar.join(" AND ")}`;
}
if (holat.limit !== undefined) sql += ` LIMIT ${holat.limit}`;
return { sql, qiymatlar: holat.qiymatlar };
},
};
return q;
}
const { sql, qiymatlar } = selectFrom("vazifalar")
.select(["id", "matn"])
.where("egasi_id", "=", 77)
.where("bajarildi", "=", false)
.limit(50)
.compile();
console.log(sql);
console.log(qiymatlar);Konsolda:
SELECT id, matn FROM vazifalar WHERE egasi_id = $1 AND bajarildi = $2 LIMIT 50
[ 77, false ]Ikki muhim narsa: qiymatlar SQL matniga qo'shilmadi — $1, $2 bo'lib qoldi (injection yo'q), va har metod q ni qaytaradi — shuning uchun zanjir (.select().where().limit()) mumkin. Haqiqiy Kysely shu g'oyaning tipli va to'liq varianti. Bizning o'yinchoq builder'da jadval va ustun nomlari tekshirilmaydi — real kutubxonada buni TypeScript tiplari qiladi.
Tekshirib ko'ring: Nega
limit(50)qiymati$3bo'lmadi, to'g'ridan SQL'ga yozildi? Bu xavflimi?
Javob
Bizning builder'da limit ni dasturchi raqam bilan beradi deb faraz qildik. Agar u foydalanuvchidan kelgan satr bo'lsa ("50; DROP TABLE"), to'g'ridan qo'shish — injection. Haqiqiy builder'lar LIMIT ni ham parametr qiladi (Prisma'ning LIMIT $2 ini yuqorida ko'rdik). Saboq: qo'lda yozilgan "kichik" builder'lar ham aynan shunday joylarda teshik ochadi.
4. Ikki ORM naqshi
4.1 Active Record
Active Record — har obyekt o'zini o'zi saqlaydi: ma'lumot va baza bilan ishlash bitta klassda.
const v = await Vazifa.findByPk(5); // Sequelize
v.matn = "Yangi matn";
await v.save(); // obyekt o'zini UPDATE qiladiSequelize va TypeORM'ning bir rejimi shunday ishlaydi; Ruby on Rails va Laravel'dan tanish naqsh. Qulay, lekin model klassi baza bilan qattiq bog'lanadi: uni bazasiz test qilish qiyin.
4.2 Data Mapper
Data Mapper — ma'lumot obyektlari oddiy, bazaga yozishni alohida qatlam (repository, client) qiladi.
const v = await db.vazifa.findUnique({ where: { id: 5n } });
await db.vazifa.update({ // client yozadi, obyekt emas
where: { id: 5n },
data: { matn: "Yangi matn" },
});Prisma shu yondashuvda: findUnique oddiy JS obyekt qaytaradi, unda save() yo'q. Bizning VazifaOmbori interfeysi (25-qism) ham Data Mapper ruhida — Vazifa turi oddiy ma'lumot, PgOmbori va keyinroq PrismaOmbori uni saqlaydi.
| Active Record | Data Mapper | |
|---|---|---|
| Kim saqlaydi | obyektning o'zi (save()) |
alohida qatlam |
| Model | baza bilan bog'liq klass | oddiy ma'lumot |
| Test | bazaga yoki mock'ga muhtoj | oddiy obyektlar bilan oson |
| Misollar | Sequelize, TypeORM (rejim) | Prisma, TypeORM (rejim), Drizzle |
5. Leaky abstraction: kanondagi uchta teshik
Leaky abstraction ("oqib turgan abstraksiya") — qulay qatlam ostidagi murakkablik bir kun baribir yuzaga chiqadi. Kanon uchta haqiqiy holatni ushladi.
5.1 TypeORM va Sequelize: yo'q ustun
"50 vazifa va ularning izohlari" so'rovi. TypeORM bilan kanonning birinchi varianti:
B (birinchi yozilishi) XATO: column distinctAlias.Vazifa_tartib does not existSequelize bilan:
B (birinchi yozilishi) XATO: column Vazifa.tartib does not existIkkalasida bir xil sabab: select da tartib yo'q, lekin ORDER BY tartib bor; relation + LIMIT bo'lganda ORM ichki subquery yasaydi va tashqi ORDER BY subquery'da yo'q ustunga ishora qiladi. Kod to'g'ri ko'rinadi, ORM esa noto'g'ri SQL yasadi. Tuzatish — tartib ni select ga qo'shish. Buni topish uchun yuborilgan SQL'ni o'qiy olish kerak edi.
5.2 Prisma: contains va %
Holat 20 da Prisma omborini yozayotganda shartnoma testi yiqildi: qidiruv "%" hamma vazifani qaytardi. Prisma 7.10 da contains (mode: "insensitive") ILIKE '%…%' ga aylanadi, lekin foydalanuvchi matnidagi % va _ belgilarini ekranlamaydi. pg ombori strpos ishlatgani uchun bu muammo yo'q edi. Yechim — \, %, _ ni o'zimiz ekranlaymiz. Bu kanon topilmasi; ORM "xavfsiz" deb ishonilgan joyda mantiq xatosi.
5.3 Prisma: matn bo'yicha saralash
Prisma orderBy da collation tanlab bo'lmaydi. vazifalar matni bo'yicha saralash kanon 26-qismda ko'rgan tartib (ORDER BY, LIMIT va OFFSET) bilan mos kelishi uchun Prisma ombori bu joyda raw SQL ga tushadi. Kanon o'lchovida aynan shu so'rov eng sekin chiqdi: pg 5.42 ms, Prisma 12.07 ms (id'lar alohida so'rov + raw SQL).
Tekshirib ko'ring: Yuqoridagi uchta holatning qaysi birida xato jim edi — dastur yiqilmadi, lekin noto'g'ri ishladi?
Javob
Prisma contains — xato bermadi, shunchaki noto'g'ri natija qaytardi (% — hamma vazifa). TypeORM va Sequelize esa Postgres xatosi bilan yiqildi — uni darhol ko'rasiz. Jim xatolarni faqat testlar ushlaydi; kanonda ham shunday bo'ldi.
6. Tezlik: o'lchangan raqamlar
6.1 Oltita kutubxona
Kanon bir xil "ro'yxat 50" so'rovini har kutubxonada 30 marta o'lchadi (mediana):
- pg (raw)1 ms
- Drizzle1,08 ms
- Knex1,09 ms
- Sequelize1,17 ms
- TypeORM1,25 ms
- Kysely1,37 ms
Manba: Kanon o'lchovi (IlmHamroh, 2026-10), katta bazasi, PostgreSQL 18.6, Node 24.21.0, 2026-10-25, mediana 30
Grafikda farq 0.4 ms dan kam: so'rovning o'zi bazada vaqt oladi, kutubxona qatlami esa kichik qo'shimcha. Bir qator bilan farqli joy — "vazifa + izohlari" so'rovi: pg bitta SQL'da json_agg bilan 1.34 ms, TypeORM ikki so'rov bilan 3.78 ms.
6.2 Prisma va pg — bir xil ombor
Prisma alohida o'lchandi: VazifaOmbori ning ikki varianti — PgOmbori va PrismaOmbori — bir xil savollar bilan:
| So'rov | pg | Prisma |
|---|---|---|
| ro'yxat 50 | 3.65 ms | 4.43 ms |
| ro'yxat + jami | 4.34 ms | 6.16 ms |
| teg filtri | 7.33 ms | 8.36 ms |
| matn bo'yicha saralash | 5.42 ms | 12.07 ms |
200 ta yangila (jami) |
2 842 ms | 4 051 ms |
Prisma hamma joyda biroz sekin, eng ko'p — u bitta SQL bilan qila olmagan joylarda (yangila: o'qish + updateMany + findUnique). Lekin bitta so'rov uchun farq millisekundlarda. Ilovani sekinlashtiradigan narsa odatda kutubxona emas, yomon so'rov: indekssiz qidiruv, N+1, ortiqcha ustunlar.
7. SQL bilmay ORM ishlatish xavfi
ORM SQL'ni yashiradi — yangi boshlovchi uchun bu yengillik bo'lib ko'rinadi. Amalda u uchta xavf tug'diradi:
- N+1 ko'rinmaydi. Sikl ichidagi
findUniquechiroyli ko'rinadi, lekin har biri alohida so'rov (N+1 muammosi). - Raw SQL "zaxira eshigi"da injection. Har ORM'da raw SQL bor. Kanon tajribasida Prisma'ning
$queryRawUnsafeiga satr ulanib berildi:"1 OR 1=1"— 48 qator (bitta foydalanuvchi o'rniga hammasi). Xuddi shu qiymat parametr bilan —22P02xatosi, ya'ni xavfsiz. Bu Prisma: raw SQL va TypedSQL darsida chuqur. - Baza imkoniyatlari yo'qoladi. Prisma sxemasi CHECK, EXCLUDE, trigger, view, RLS'ni ifodalay olmaydi (keyingi darsda introspection buni ko'rsatadi). SQL bilmagan dasturchi bu himoyalarni umuman qo'ymaydi.
Kursning yondashuvi shuning uchun 26-qismda SQL, 27-qismda ORM: avval bazani tushunasiz, keyin unga qulay qatlam qo'shasiz.
8. Qachon qaysi biri
| Vaziyat | Tavsiya |
|---|---|
| Oddiy CRUD, ko'p model, jamoada turli tajriba | ORM (Prisma) |
| Murakkab so'rovlar, SQL ustidan to'liq nazorat, lekin tiplar kerak | Query builder (Kysely, Drizzle) |
| Hisobot, tahliliy so'rov, maxsus Postgres imkoniyati | Raw SQL (ORM ichida ham) |
| Eski loyihani qo'llab-quvvatlash | qaysi bo'lsa — o'shani o'qiy olish (TypeORM, Sequelize, Knex) |
Kanon qarori (o'lchov bilan): vazifalar-api uchun asosiy — Prisma, taqqoslash va murakkab joylar uchun — pg va raw SQL. Kutubxonalarni chuqur taqqoslash — ORM tanlash qo'llanmasi.
Amalda ko'p jamoalar aralash ishlaydi: 90 % so'rov ORM orqali, qolgan 10 % — raw SQL. Bu xato emas, me'yor.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
column … does not exist faqat relation + limit bilan |
ORM subquery'si | saralash ustunini select ga qo'shish, SQL logini o'qish |
qidiruvda % hamma narsani topadi |
LIKE belgilari ekranlanmagan |
\, %, _ ni ekranlash |
| ro'yxat sahifasi 100 ta so'rov yuboradi | N+1 | include/JOIN, so'rov logini yoqish |
$queryRawUnsafe bilan hamma qator |
satr ulash | tagged $queryRaw yoki parametr |
JSON.stringify xatosi |
bigint | Number() / satrga aylantirish (Prisma: modellar) |
10. Mashqlar
Mashqlar kurs/mashqlar/27/19-orm/ da. 2- va 3-mashq Node 24 da tekshirilgan (biz ishga tushirdik).
1-mashq (oson): qaysi daraja
pool.query("SELECT … WHERE id = $1", [5])—db.selectFrom("vazifalar").where("id", "=", 5)—db.vazifa.findUnique({ where: { id: 5n } })—- Obyekt o'zini
save()bilan saqlaydi — - Saqlashni alohida qatlam qiladi —
Yechim
- raw SQL — SQL matni sizniki.
- query builder — jadval va ustun nomlari bor, lekin SQL'ni metodlar yig'adi.
- ORM — model (
vazifa) va maydonlar; jadval nomi sxemada. - Active Record — Sequelize, Rails.
- Data Mapper — Prisma, bizning
VazifaOmbori.
2-mashq (o'rta): builder'ga orderBy
«Query builder ichidan» bo'limidagi selectFrom ga orderBy(ustun, yonalish) metodini qo'shing: bir necha marta chaqirilsa, hammasi vergul bilan ORDER BY ga yig'ilsin; yonalish faqat "asc" yoki "desc" bo'lsin, boshqasi — xato. ORDER BY WHERE dan keyin, LIMIT dan oldin keladi.
Yechim
function selectFrom(jadval) {
const h = { ustunlar: ["*"], shartlar: [], qiymatlar: [] };
h.tartib = [];
const q = {
select(u) { h.ustunlar = u; return q; },
where(ustun, op, qiymat) {
h.qiymatlar.push(qiymat);
h.shartlar.push(`${ustun} ${op} $${h.qiymatlar.length}`);
return q;
},
orderBy(ustun, yonalish = "asc") {
if (yonalish !== "asc" && yonalish !== "desc") {
throw new Error(`noto'g'ri yo'nalish: ${yonalish}`);
}
h.tartib.push(`${ustun} ${yonalish.toUpperCase()}`);
return q;
},
limit(n) { h.limit = n; return q; },
compile() {
let sql = `SELECT ${h.ustunlar.join(", ")} FROM ${jadval}`;
if (h.shartlar.length > 0) {
sql += ` WHERE ${h.shartlar.join(" AND ")}`;
}
if (h.tartib.length > 0) {
sql += ` ORDER BY ${h.tartib.join(", ")}`;
}
if (h.limit !== undefined) sql += ` LIMIT ${h.limit}`;
return { sql, qiymatlar: h.qiymatlar };
},
};
return q;
}
const { sql } = selectFrom("vazifalar")
.select(["id"])
.where("egasi_id", "=", 77)
.orderBy("tartib")
.orderBy("id", "desc")
.limit(50)
.compile();
console.log(sql);
try {
selectFrom("vazifalar").orderBy("id", "DESC; DROP TABLE vazifalar");
} catch (e) {
console.log(e.message);
}Konsolda:
SELECT id FROM vazifalar WHERE egasi_id = $1 ORDER BY tartib ASC, id DESC LIMIT 50
noto'g'ri yo'nalish: DESC; DROP TABLE vazifalarORDER BY da yo'nalish va ustun nomini parametr qilib bo'lmaydi ($1 faqat qiymat uchun) — shuning uchun oq ro'yxat bilan tekshirdik (Parametrli so'rovlar darsidagi qoida). Ustun nomi uchun ham real kutubxonada oq ro'yxat (tiplar) bor.
3-mashq (qiyin): bo'sh qiymatga chidamli butun son
Loyiha qadamidagi Butun g'oyasini Zod'siz yozing: butunOl(qiymat, sukut, { min, max }). undefined → sukut; faqat raqamlardan iborat satr → son; boshqa hamma narsa ("", " 80", "1e3") → Error; chegaradan tashqari → Error. Tekshiring: undefined, "8080", "", " 80", "70000" (max 65 535).
Ishora: /^\d+$/.test(x) (RegExp asoslari va metodlari); xatoni try/catch bilan ushlab, message ni chiqaring.
Yechim
function butunOl(qiymat, sukut, { min = 0, max = Infinity } = {}) {
if (qiymat === undefined) return sukut;
if (!/^\d+$/.test(qiymat)) {
throw new Error(`butun son emas: ${JSON.stringify(qiymat)}`);
}
const n = Number(qiymat);
if (n < min || n > max) {
throw new Error(`chegaradan tashqari: ${n}`);
}
return n;
}
for (const x of [undefined, "8080", "", " 80", "70000"]) {
try {
const n = butunOl(x, 3000, { max: 65535 });
console.log(JSON.stringify(x), "→", n);
} catch (e) {
console.log(JSON.stringify(x), "→", e.message);
}
}Konsolda:
undefined → 3000
"8080" → 8080
"" → butun son emas: ""
" 80" → butun son emas: " 80"
"70000" → chegaradan tashqari: 70000JSON.stringify(undefined) — undefined (qo'shtirnoqsiz), bo'sh satr esa "" — shu farq aynan biz qidirayotgan xato: "o'zgaruvchi yo'q" va "o'zgaruvchi bor, lekin bo'sh" — har xil narsa.
11. Loyiha qadami: holat 19 — eski teshiklarni yopish
ORM qo'shishdan oldin kanon vazifalar-api dagi 25–26-qismlardan qolgan uchta teshikni yopdi. Bu ORM bilan bevosita bog'liq emas, lekin keyingi darslardagi prisma ombori shu config va pool ustiga quriladi — poydevor toza bo'lsin.
11.1 PORT= — jim 0
25-qism config'ida raqamlar z.coerce.number() bilan o'qilardi. JavaScript'da:
console.log(Number("")); // 0
console.log(Number(" 80")); // 80
console.log(Number("1e3")); // 1000.env da PORT= (qiymatsiz) qolsa, Number("") — 0, va server xato bermasdan tasodifiy portda ishga tushardi (port 0 — operatsion tizim bo'sh portni o'zi tanlaydi). Xuddi shu teshik PG_STATEMENT_TIMEOUT= da ham bor edi — bo'sh qiymat timeout'ni jim o'chirardi (0 = cheksiz, Monitoring darsi). Kanon Butun yordamchisini yozdi (qatorlar telefon uchun bo'lindi, mazmuni aynan):
const Butun = (sukut: number) =>
z.string()
.regex(/^\d+$/, {
error: "butun son bo'lishi kerak (bo'sh qiymat — xato)",
})
.default(String(sukut))
.transform(Number);
// ishlatilishi:
PORT: Butun(3000).pipe(z.number().max(65_535)),
EGASI_ID: Butun(1).pipe(z.number().positive()),
PG_POOL_MAX: Butun(10).pipe(z.number().min(1).max(100)),
PG_STATEMENT_TIMEOUT: Butun(5000),.default faqat o'zgaruvchi umuman yo'q bo'lganda ishlaydi; bo'sh satr — regex'dan o'tmaydi va ilova ishga tushmaydi. "0" esa ataylab ruxsat etilgan — testlar tasodifiy port so'raydi.
11.2 CORS: oxiridagi /
CORS_ORIGINS="https://login.github.io/" — yaroqli URL, lekin brauzer Origin sarlavhasini / siz yuboradi, cors() esa satrlarni aynan solishtiradi — Access-Control-Allow-Origin chiqmasdi. Yechim — URL.origin ga normallash:
const u = new URL("HTTPS://Login.GitHub.io:443/");
console.log(u.origin); // https://login.github.io
const yolli = new URL("https://a.example/x/");
console.log(yolli.pathname); // /x/Kanondagi Origin sxemasi pathname !== "/", search yoki hash bo'lsa rad etadi ("faqat origin bo'lsin") va qolganini u.origin qilib qaytaradi.
11.3 Pool xatosi jim yutilardi
26-qismdagi omborYarat da: pool.on("error", () => {}) — bo'sh ulanish uzilsa, jarayon yiqilmasin. To'g'ri niyat, lekin xato hech qayerda ko'rinmasdi. Endi log'ga yoziladi:
pool.on("error", (err) => {
log?.error({ err }, "pg pool: bo'sh ulanish uzildi");
});server.ts omborYarat(config, log) ga pino logger'ini beradi. Regressiya testi pg_terminate_backend bilan bo'sh ulanishni uzadi va log'da bitta terminating connection due to administrator command yozuvi borligini, keyingi yozuv yangi ulanishda ishlaganini tekshiradi.
11.4 Flaky testlar
Kanon 26-qism testlaridan ikkitasi ba'zan yiqilayotganini topdi ("tranzaksiya paytida ulanish uzilsa" — 8 urinishdan 2 marta). Sabab: test bo'sh ulanishni uzardi, pool esa uni almashtirib qo'yishi mumkin edi. Endi test ulanishni aynan tranzaksiya ichida, qulf kutayotgan paytda uzadi — 10/10 o'tdi. Ikkinchisi — yuklangan mashinada ulanish timeout'i (2 s → 10 s).
11.5 Tekshiruv va commit
Testlar: test/config.test.ts (+2: bo'sh raqam, CORS normallash), test/supertest.test.ts (+1: …/ bilan haqiqiy so'rovda sarlavha chiqadi), test/pg-ombor.test.ts (+1: pool xatosi log'ga). Kanon tekshiruvi: npm run tip va npm run lint — exit 0, npm test — 88/88, Postgres testlari 18/18, jonli 9 ta so'rov — oldingidek.
git add vazifalar-api
M="fix: config — bo'sh raqam xato, CORS origin normallanadi"
T="pg pool xatosi log'ga; flaky testlar"
git commit -m "$M" -m "$T"12. Real ishda
- Node.js ekotizimida eng ko'p uchraydiganlari: Prisma va Drizzle (yangi loyihalar), TypeORM va Sequelize (eski loyihalar, NestJS'ning eski shablonlari), Knex (migratsiya vositasi sifatida ham).
- Katta kompaniyalarda ko'pincha ORM + raw SQL aralash; tahliliy so'rovlar alohida, ko'pincha replika bazada.
- Intervyuda: "ORM afzalligi va kamchiligi?", "Active Record va Data Mapper farqi?", "ORM qanday N+1 ga olib keladi?", "ORM'da SQL injection bo'lishi mumkinmi?"
Xulosa
- Uch daraja: raw SQL (nazorat), query builder (tipli SQL), ORM (modellar) — uchalasi ham oxirida SQL yuboradi.
- Active Record — obyekt o'zini saqlaydi; Data Mapper — alohida qatlam (Prisma, bizning ombor).
- Abstraksiya oqadi: TypeORM/Sequelize yo'q ustunga ishora qildi, Prisma
contains%ni ekranlamadi — testlar va SQL logi ushlaydi. - Kutubxonalar orasidagi farq bitta so'rovda millisekundlar (oltita kutubxonada < 0.4 ms, Prisma pg dan odatda 0.4–6.7 ms sekinroq); asosiy sekinlik — yomon so'rovdan.
- Holat 19: bo'sh raqamli o'zgaruvchi endi xato, CORS origin normallanadi, pool xatosi log'ga yoziladi.
Keyingi dars: Prisma 7: o'rnatish va arxitektura — Rust'siz Prisma, driver adapter, prisma.config.ts va mavjud bazani introspection qilish.
Manbalar
- Prisma — ORM, query builder va SQL taqqoslovi: https://www.prisma.io/docs/orm/v7
- Kysely: https://kysely.dev/docs/intro; Drizzle: https://orm.drizzle.team/docs/overview
- Martin Fowler — Active Record, Data Mapper (Patterns of Enterprise Application Architecture): https://martinfowler.com/eaaCatalog/activeRecord.html, https://martinfowler.com/eaaCatalog/dataMapper.html
- Joel Spolsky — The Law of Leaky Abstractions (2002): https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/
- O'qilgan sana — 2026-10-26. Kanon o'lchovi (IlmHamroh, 2026-10): oltita kutubxona va TypeORM/Sequelize xatolari, pg va Prisma ombori,
$queryRawUnsafe— 48 qator, holat 19 tekshiruvi (tip, lint, 88/88). Biz ishga tushirdik: Prisma 7.10.1 SQL logi (PostgreSQL 17.6, holat 20 bazasi), JS misollar va 2–3-mashqlar (Node 24).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!