IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis19/65-dars18 daqiqa
Mundarija (34)

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-api ning 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.
  • rows ning turi any ga 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 vazifa modeli bilan ishlaysiz, SQL'ni u yozadi.
  • Raw SQL ("xom" SQL) — SQL matnini o'zingiz yozib, pg bilan 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 (katta bazasi, 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

ts
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

ts
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

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

text
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 $3

Prisma 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| D

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

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

text
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 $3 bo'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.

ts
const v = await Vazifa.findByPk(5);   // Sequelize
v.matn = "Yangi matn";
await v.save();                         // obyekt o'zini UPDATE qiladi

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

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

text
B (birinchi yozilishi) XATO: column distinctAlias.Vazifa_tartib does not exist

Sequelize bilan:

text
B (birinchi yozilishi) XATO: column Vazifa.tartib does not exist

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

Ro'yxat 50: mediana vaqt
  • 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:

  1. N+1 ko'rinmaydi. Sikl ichidagi findUnique chiroyli ko'rinadi, lekin har biri alohida so'rov (N+1 muammosi).
  2. Raw SQL "zaxira eshigi"da injection. Har ORM'da raw SQL bor. Kanon tajribasida Prisma'ning $queryRawUnsafe iga satr ulanib berildi: "1 OR 1=1" — 48 qator (bitta foydalanuvchi o'rniga hammasi). Xuddi shu qiymat parametr bilan — 22P02 xatosi, ya'ni xavfsiz. Bu Prisma: raw SQL va TypedSQL darsida chuqur.
  3. 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
js
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:

text
SELECT id FROM vazifalar WHERE egasi_id = $1 ORDER BY tartib ASC, id DESC LIMIT 50
noto'g'ri yo'nalish: DESC; DROP TABLE vazifalar

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

text
undefined → 3000
"8080" → 8080
"" → butun son emas: ""
" 80" → butun son emas: " 80"
"70000" → chegaradan tashqari: 70000

JSON.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:

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

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

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

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

bash
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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
ORM, query builder va raw SQL: uch abstraksiya darajasi va to'g'ri tanlov — IlmHamroh