18 β Prisma ORM¶
β¬ οΈ Oldingi: 17 β MySQL: mysql2, pool, tranzaksiya Β· π README Β· Keyingi: 19 β MongoDB va Mongoose β‘οΈ
Bu bobda: Oldingi ikki bobda biz SQL ni qo'lda yozdik β
better-sqlite3vamysql2bilanINSERT,SELECT,JOINlarni o'z barmoqlarimiz bilan terib chiqdik. Bu bobda boshqa yo'lni β ORM ni o'rganamiz. Avval ORM nima ekanini (obyekt bilan jadval orasidagi ko'prik) va uning narxini tushunamiz. So'ng zamonaviy, type-safe ORM β Prisma bilan tanishamiz: o'rnatish (prisma init),schema.prismafaylining tuzilishi (datasource,generator,model, maydon turlari va atributlar@id @default @unique @relation @updatedAt),migrate devorqali schema'dan haqiqiy DB jadvallari yasash va migration tarixi,generateorqali type-safe client. Keyin to'liq CRUD ni βcreate,findMany,findUnique,findFirst,update,deleteva ularningwhere/select/orderBy/take/skipopsiyalarini β ko'ramiz. Relation (1:N va N:M),include, nested create,$transaction, seed, Prisma Studio va$queryRawni o'rganamiz. REAL KEYS: foydalanuvchi + vazifa (1:N) tizimini Prisma bilan to'liq quramiz. Hamma kod Node 24.12 + Prisma 6.19 + SQLite da haqiqatanmigrate/generate/CRUDqilib ishga tushirib tekshirilgan.
ORM nima va nega kerak?¶
Oldingi boblarda baza bilan ishlash shunday ko'rinardi:
const row = db.prepare("SELECT * FROM users WHERE id = ?").get(5);
console.log(row.name); // row β oddiy obyekt, lekin SQL ni biz yozdik
Bu yondashuvda SQL tilini biz boshqaramiz: har bir so'rovni qo'lda yozamiz, ustun nomlarini matn ichida teramiz, natijani obyektga o'zimiz moslaymiz. Kichik loyihada bu joyida. Lekin loyiha o'sganda muammolar boshlanadi:
- Ustun nomini matn ichida adashtirsangiz (
naemo'rniganame), xato ishga tushgancha bilinmaydi β runtime'daundefinedchiqadi. - Bir nechta jadvalni
JOINqilib, natijani ichma-ich obyektga aylantirish (user.tasks) β qo'lda zerikarli va xatoga moyil. - Baza tuzilmasi o'zgarsa (yangi ustun qo'shildi), uni qidirib hamma SQL ni qo'lda yangilash kerak.
ORM (Object-Relational Mapping) β aynan shu muammoni hal qiladi. Bu β obyekt dunyosi (JavaScript klasslari/obyektlari) bilan relyatsion baza dunyosi (jadvallar, qatorlar, ustunlar) o'rtasidagi ko'prik. ORM bilan siz SQL emas, obyekt va metodlar tilida gaplashasiz:
const user = await prisma.user.findUnique({ where: { id: 5 } });
// SQL ni Prisma yozib beradi; user β turi aniq obyekt
ORM ning foydasi:
- Qo'lda SQL kamayadi β
INSERT/SELECT/JOINlarni metodlar bilan ifodalaysiz. - Xavfsizlik β parametrlar avtomatik bog'lanadi, SQL injection xavfi yo'qoladi (oldingi bobdagi
?o'rinbosarlari kabi, lekin avtomatik). - Tuzilma bir joyda β baza modeli kod bilan bir manbada tasvirlanadi.
Lekin ORM tekin emas β narxi bor:
- Abstraksiya qatlami β siz baza bilan to'g'ridan-to'g'ri emas, ORM orqali gaplashasiz. Ba'zan u yaratgan SQL eng tez variant bo'lmaydi.
- Murakkab so'rovlar β chuqur analitik so'rovlar (oyna funksiyalari, murakkab agregatsiya) ORM tili bilan noqulay bo'lishi mumkin. Bunday holatda raw SQL ga qaytasiz (quyida
$queryRawni ko'ramiz). - N+1 muammosi β relation larni e'tiborsiz yuklasangiz, bitta so'rov o'rniga yuzta so'rov ketishi mumkin (Prisma
includebu xavfni kamaytiradi).
Xulosa: ORM sehrli kaltakcha emas β bu vosita. SQL ni bilish baribir muhim (chuqurroq uchun
../sql/README.md). ORM esa kundalik CRUD ni tezlashtiradi va kodni xavfsiz, o'qishli qiladi.
Prisma nima?¶
Node ekotizimida bir nechta ORM bor (Sequelize, TypeORM, Drizzle, ...). Bu kitobda biz Prisma ni tanlaymiz β chunki u zamonaviy, type-safe va o'rganish uchun eng aniq modelga ega.
Prisma boshqa ORM lardan farqli o'laroq schema-asosli. Ya'ni siz modellaringizni JavaScript klasslari yoki dekoratorlar bilan emas, alohida schema.prisma faylida β o'qish uchun qulay, deklarativ tilda β tasvirlaysiz. Bu fayl yagona haqiqat manbai (single source of truth) bo'ladi. Undan Prisma ikki narsa yasaydi:
- Migration β schema'dan haqiqiy DB jadvallari (
CREATE TABLE ...). - Type-safe client β kodingiz uchun avtoto'ldirishli, turlari aniq JavaScript/TypeScript kutubxonasi.
Prisma ning ustun tomonlari:
- Type-safety β
prisma.user.findMany()qaytaradigan obyektning maydonlari oldindan ma'lum. TypeScript da xato yozsangiz, kod yozayotganda (compile vaqtida) qizil chiziq chiqadi, runtime'da emas. JavaScript da ham muharrir avtoto'ldirishni ko'rsatadi. - Migration tizimi β schema'ni o'zgartirsangiz, Prisma farqni hisoblab, yangi migration SQL faylini yozadi va tarixini saqlaydi (xuddi git kabi).
- Zamonaviy DX β
prisma studio(vizual baza brauzeri), aniq xato xabarlari,include/selectbilan o'qishli so'rovlar. - Ko'p baza β bitta API bilan SQLite, PostgreSQL, MySQL, SQL Server, MongoDB bilan ishlaydi (datasource'ni almashtirasiz).
Bu bobda biz SQLite ni ishlatamiz β server o'rnatish shart emas, baza shunchaki bitta .db fayl. Bu Prisma'ni o'rganishni maksimal soddalashtiradi. Keyin MySQL'ga o'tish faqat datasource ni o'zgartirishdan iborat.
O'rnatish va prisma init¶
Yangi loyiha yaratamiz. ESM ishlatamiz, shuning uchun package.json da "type": "module":
Endi Prisma ni o'rnatamiz. Ikki paket bor va ularning roli har xil:
# prisma β CLI asbobi (migrate, generate, studio). Faqat ishlab chiqishda kerak.
npm install -D prisma@6
# @prisma/client β ilovangiz ishlatadigan kutubxona (CRUD). Productionda ham kerak.
npm install @prisma/client@6
Nega ikkita?
prismaβ bu terminal asbobi (siz buyruq berasiz).@prisma/clientβ bu kodingiz import qiladigan kutubxona. BirinchisidevDependenciesga, ikkinchisi oddiydependenciesga tushadi.Nega
@6deb yozdik? Versiyani ataylab qadab qo'ydik. Oddiynpm install prismabugun eng yangi Prisma 7 ni o'rnatadi β unda esa schema sintaksisi o'zgargan (urlschema'danprisma.config.tsga ko'chgan, providerprisma-clientbo'lgan). Agar 7-versiyada bu bobdagi aynan shu schema'ni ishlatsangiz, birinchimigrate devda xato chiqadi:P1012 β The datasource property url is no longer supported in schema files. Shu bois biz butun bobni Prisma 6 (eng keng tarqalgan, soddaroq oqim) ga moslab, versiyani@6bilan qadadik. Bu bilan siz quyidagi har bir buyruq va chiqishni xuddi shu ko'rinishda takrorlay olasiz.
Endi loyihani ishga tayyorlaymiz:
Bu buyruq quyidagilarni yaratadi:
prisma/schema.prismaβ asosiy schema fayli (datasource SQLite ga moslangan)..envβ maxfiy sozlamalar uchun (masalan, DB ulanish manzili)..gitignoreganode_modulesva.envqo'shiladi.
Versiyani tekshiramiz:
Eslatma (Prisma 7+): Eng yangi Prisma 7 da sozlash biroz boshqacha (
urlprisma.config.tsga ko'chgan, providerprisma-client, driver adapter ishlatiladi). Bu bobda biz keng tarqalgan, soddaroq Prisma 6 oqimidan foydalanamiz β u eng ko'p qo'llanmalar va loyihalarga mos keladi, shu sababli yuqorida versiyani@6bilan qadadik. Agarnpx prisma --versionsizda6.xo'rniga7.xko'rsatsa, qaytadan qadab o'rnating:npm install -D prisma@6 @prisma/client@6. Asoslar (schema, migrate, generate, CRUD) ikkala versiyada ham bir xil.
schema.prisma β yurak fayli¶
prisma/schema.prisma faylini ochamiz. U uch qismdan iborat: datasource, generator va model lar.
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
createdAt DateTime @default(now())
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
title String
done Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
}
Har bir qismni ajratib tushunamiz.
datasource β qaysi baza?¶
datasource db {
provider = "sqlite" // baza turi: sqlite | postgresql | mysql | ...
url = "file:./dev.db" // ulanish manzili (SQLite uchun β fayl yo'li)
}
provider β qaysi bazani ishlatishimizni aytadi. SQLite uchun url β bu prisma/ papkasiga nisbatan fayl yo'li. MySQL ga o'tish kerak bo'lsa, bu blok shunday bo'lardi:
datasource db {
provider = "mysql"
url = env("DATABASE_URL") // .env dan: mysql://root@localhost:3306/nodejs_test
}
Maxfiy ma'lumotni .env faylida saqlash yaxshi amaliyot (env("DATABASE_URL")). SQLite uchun esa biz qulaylik uchun to'g'ridan-to'g'ri fayl yo'lini yozdik.
generator β nimani yasaymiz?¶
generator β prisma generate buyrug'i nima yasashini aytadi. Bizga prisma-client-js kerak: u @prisma/client ichiga type-safe CRUD kutubxonasini joylaydi.
model β jadvallar¶
Har bir model β bazadagi bitta jadval (va kodda bitta tur). Maydonlar nom Tur atributlar shaklida yoziladi.
Skalyar turlar (asosiylari): Int, String, Boolean, DateTime, Float, Decimal, Bytes, Json (baza qo'llasa). ? qo'shsangiz β maydon ixtiyoriy bo'ladi (String? β null bo'lishi mumkin).
Atributlar (@ bilan boshlanadi) β maydonga qoidalar qo'shadi:
| Atribut | Ma'nosi |
|---|---|
@id |
Birlamchi kalit (primary key) |
@default(autoincrement()) |
Avtomatik o'suvchi raqam (1, 2, 3, ...) |
@default(now()) |
Yaratilgan paytdagi vaqt |
@default(false) |
Boshlang'ich qiymat |
@unique |
Takrorlanmas (masalan, email) |
@updatedAt |
Har update da avtomatik yangilanadigan vaqt |
@relation(...) |
Modellar orasidagi bog'lanish (relation) |
tasks Task[] qatori β bu relation maydoni (bazada ustun emas). U "bitta User ko'p Task ga ega" degan 1:N bog'lanishni bildiradi. Relation larni keyingi bo'limda chuqur ko'ramiz.
migrate dev β schema'dan baza yasash¶
Schema tayyor β endi undan haqiqiy baza jadvallarini yasaymiz. Buni migration qiladi:
--name init β bu migration'ga nom beradi (xuddi git commit xabari kabi). Buyruq nima qiladi:
- Schema'ni hozirgi baza holati bilan solishtiradi.
- Farqni amalga oshiradigan SQL faylini yozadi (
prisma/migrations/.../migration.sql). - Bu SQL ni bazaga qo'llaydi (jadvallarni yaratadi).
- So'ng avtomatik
prisma generateni chaqirib, client'ni yangilaydi.
Haqiqiy chiqishi (biz ishga tushirdik):
SQLite database dev.db created at file:./dev.db
Applying migration `20260612080302_init`
The following migration(s) have been created and applied from new schema changes:
prisma/migrations/
ββ 20260612080302_init/
ββ migration.sql
Your database is now in sync with your schema.
β Generated Prisma Client (v6.19.3) to ./node_modules/@prisma/client
Prisma yozgan migration.sql faylini ochib ko'rsak β bu oddiy, tanish SQL (oldingi boblardan):
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "Task" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"done" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
Diqqat qiling: @id @default(autoincrement()) -> PRIMARY KEY AUTOINCREMENT, @unique -> UNIQUE INDEX, @relation -> FOREIGN KEY. Schema'dagi har bir atribut SQL ga aylandi. Endi siz Prisma "sehrini" emas, uning ostidagi haqiqiy SQL ni ko'rdingiz.
Migration tarixi β nega muhim?¶
Har bir migrate dev yangi papka yaratadi (20260612080302_init). Bu tarix β bazaning qanday rivojlanganini ko'rsatadi. Schema'ni o'zgartirsangiz (masalan, Task ga priority Int ustun qo'shsangiz), yana npx prisma migrate dev --name add_priority chaqirasiz β Prisma faqat farqni (ALTER TABLE) yozadi. Bu migration fayllari git ga commit qilinadi: jamoangiz va production server bir xil ketma-ketlikda bazani qurib oladi (prisma migrate deploy).
migrate devvsmigrate deploy:migrate devβ ishlab chiqish uchun (yangi migration yaratadi). Production'da esaprisma migrate deployishlatiladi β u faqat mavjud migration'larni qo'llaydi, yangi yaratmaydi. CI/deploy haqida../git-github/README.mdga qarang.
generate β type-safe client¶
migrate dev generate ni o'zi chaqirdi. Lekin schema'ni o'zgartirib, migration yaratmasdan faqat client'ni yangilamoqchi bo'lsangiz (yoki node_modules ni qaytadan o'rnatgandan keyin):
Bu @prisma/client ichiga sizning modellaringizga moslangan kodni yozadi. Endi import { PrismaClient } from "@prisma/client" qilganda, prisma.user va prisma.task mavjud bo'ladi β turlari, maydonlari va metodlari bilan.
CRUD β to'liq amaliyot¶
Endi qiziqarli qismga keldik. Birinchi faylimizni yozamiz. Prisma client'ni bir marta yaratamiz va uni qayta ishlatamiz:
// db.js β bitta umumiy PrismaClient nusxa
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient();
Muhim: Butun ilovada bitta
PrismaClientnusxasini yarating. Har so'rovda yanginew PrismaClient()qilsangiz, ulanishlar tugab qoladi. Express ilovasida uni bir marta yaratib, hamma joyda import qilasiz.
CREATE β create¶
import { prisma } from "./db.js";
const ali = await prisma.user.create({
data: { email: "ali@example.com", name: "Ali" },
});
console.log(ali); // { id: 1, email: 'ali@example.com', name: 'Ali', createdAt: ... }
data β yaratiladigan qatorning maydonlari. id, createdAt ni yozmaymiz β ular @default orqali avtomatik to'ladi. Qaytadigan qiymat β to'liq yaratilgan obyekt (id bilan birga).
READ β findMany, findUnique, findFirst¶
// Hammasini olish
const hamma = await prisma.user.findMany();
// Unique maydon (id yoki @unique) bo'yicha bitta qator
const u = await prisma.user.findUnique({ where: { email: "ali@example.com" } });
// Birinchi mos kelgan (har qanday shart bo'yicha)
const birinchi = await prisma.task.findFirst({ where: { done: false } });
findUnique faqat unique maydon bo'yicha qidiradi (id yoki @unique belgilangan). findFirst β har qanday shart bo'yicha birinchi mos kelganni qaytaradi. Topilmasa ikkalasi ham null qaytaradi.
So'rov opsiyalari: where, select, orderBy, take, skip¶
const vazifalar = await prisma.task.findMany({
where: { done: false }, // filtr: faqat bajarilmaganlar
select: { id: true, title: true }, // faqat shu maydonlar (boshqasi olinmaydi)
orderBy: { id: "asc" }, // tartiblash (asc | desc)
take: 10, // ko'pi bilan 10 ta (LIMIT)
skip: 0, // boshidan necha tani tashlab ketish (OFFSET)
});
Bu β sahifalash (pagination) ning asosi: take (sahifa hajmi) va skip ((sahifa - 1) * hajm). select β faqat kerakli ustunlarni olib, tarmoqni tejaydi. where esa boy filtrlarni qo'llaydi:
// Murakkab filtr: nomida "Prisma" bor VA bajarilmagan
await prisma.task.findMany({
where: {
done: false,
title: { contains: "Prisma" }, // qisman moslik
// boshqa operatorlar: gt, gte, lt, lte, in, notIn, startsWith, endsWith
},
});
UPDATE β update¶
const yangilangan = await prisma.task.update({
where: { id: 1 }, // qaysi qatorni (unique bo'yicha)
data: { done: true }, // nimani o'zgartirish
});
@updatedAt belgilagan updatedAt maydoni bu yerda avtomatik yangilanadi. Bir nechta qatorni birdan yangilash uchun updateMany({ where, data }) bor.
DELETE β delete¶
const ochirilgan = await prisma.task.delete({ where: { id: 1 } });
// bir nechta: prisma.task.deleteMany({ where: { done: true } })
Diqqat β relation tartibi:
Task.userIdUserga ishora qiladi (foreign key). Shuning uchun User ni o'chirishdan oldin uning Task larini o'chirish kerak (yoki schema'daonDelete: Cascadeqo'yish). Aks holda baza "RESTRICT" cheklovi bilan xato beradi.
Relations β modellar orasidagi bog'lanish¶
Haqiqiy ilovalar bir nechta bog'langan jadvaldan iborat. Prisma relation larni juda qulay boshqaradi.
1:N β bitta User, ko'p Task¶
Schema'da bu shunday tasvirlangan edi:
model User {
id Int @id @default(autoincrement())
tasks Task[] // "User ko'p Task ga ega" tomoni
}
model Task {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id]) // bog'lanish
userId Int // tashqi kalit (FK)
}
Ikki maydonni ajratish muhim:
userId Intβ bu haqiqiy ustun bazada (foreign key). U qaysi User ga tegishliligini saqlaydi.user User @relation(...)vatasks Task[]β bular virtual relation maydonlari. Bazada ustun emas β Prisma ularni so'rovda bog'lash uchun ishlatadi.
@relation(fields: [userId], references: [id]) β Prisma ga "Task.userId ustuni User.id ga ishora qiladi" deb aytadi.
include β relation ni birga yuklash¶
Foydalanuvchini uning vazifalari bilan birga olish uchun include ishlatamiz:
const u = await prisma.user.findUnique({
where: { id: 1 },
include: { tasks: true }, // vazifalarni ham yukla
});
console.log(u.name, u.tasks); // u.tasks β massiv
include siz u.tasks bo'lmaydi (faqat User maydonlari keladi). include bilan esa Prisma kerakli JOIN/so'rovni o'zi qiladi va natijani ichma-ich obyekt ko'rinishida beradi β qo'lda JOIN ni unutdik.
include ichida yana filtr/tartib qo'shsa bo'ladi:
const u = await prisma.user.findUnique({
where: { id: 1 },
include: {
tasks: { where: { done: true }, orderBy: { createdAt: "desc" } },
},
});
// u.tasks β faqat bajarilgan vazifalar, eng yangisi birinchi
Nested create β bog'langan yozuvni birga yaratish¶
Eng kuchli xususiyatlardan biri: User va uning Task larini bitta create da yaratish:
const ali = await prisma.user.create({
data: {
email: "ali@example.com",
name: "Ali",
tasks: {
create: [
{ title: "Prisma o'rganish" },
{ title: "Loyiha yozish" },
],
},
},
include: { tasks: true }, // natijada vazifalarni ham qaytar
});
console.log(ali.tasks.length); // 2
Diqqat: ichki create da biz userId ni yozmadik β Prisma uni avtomatik bog'laydi. Bu β relation bilan ishlashni ajoyib soddalashtiradi.
N:M β ko'pdan-ko'pga (qisqacha)¶
N:M (masalan, Post va Tag β bitta post ko'p tag, bitta tag ko'p postda) ham oson. Prisma implicit (yashirin) bog'lovchi jadval yaratadi:
model Post {
id Int @id @default(autoincrement())
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
posts Post[]
}
Ikkala tomonda [] yozish kifoya β Prisma o'rtadagi _PostToTag jadvalini o'zi boshqaradi. connect bilan mavjud yozuvlarni bog'laysiz:
await prisma.post.create({
data: {
title: "Salom",
tags: { connect: [{ id: 1 }, { id: 2 }] }, // mavjud taglarga bog'la
},
});
$transaction β hammasi yoki hech narsa¶
Oldingi MySQL bobidan tranzaksiyani eslang: bir nechta amal birga muvaffaqiyatli bo'lishi yoki birga bekor bo'lishi kerak. Prisma da ikki shakli bor.
1. Massiv shakli β mustaqil amallarni atomik bajarish:
const [userlar, tasklar] = await prisma.$transaction([
prisma.user.count(),
prisma.task.count(),
]);
2. Interaktiv (callback) shakli β bir amal natijasi ikkinchisiga kerak bo'lganda:
const natija = await prisma.$transaction(async (tx) => {
const t = await tx.task.create({ data: { title: "Transfer", userId: 1 } });
await tx.task.update({ where: { id: t.id }, data: { done: true } });
return tx.task.findUnique({ where: { id: t.id } });
});
Callback ichida prisma o'rniga tx ni ishlatasiz. Agar callback xato tashlasa, hamma amallar rollback qilinadi (bekor bo'ladi). Bu β pul o'tkazmasi, buyurtma yaratish kabi "yarim bajarilmasligi kerak" amallar uchun.
Seed β bazani boshlang'ich ma'lumot bilan to'ldirish¶
Ishlab chiqishda bazaga test ma'lumotini tez to'ldirish kerak bo'ladi β bu seed. Oddiy seed skripti:
// prisma/seed.js
import { prisma } from "../db.js";
async function seed() {
await prisma.task.deleteMany(); // toza boshlash
await prisma.user.deleteMany();
await prisma.user.create({
data: {
email: "admin@example.com",
name: "Admin",
tasks: { create: [{ title: "Sozlamalar" }, { title: "Hisobot" }] },
},
});
console.log("Seed tugadi");
}
seed().finally(() => prisma.$disconnect());
package.json ga seed buyrug'ini sozlab qo'yasiz:
Endi npx prisma db seed yoki har migrate reset da seed avtomatik ishlaydi. Bu jamoa uchun "bir buyruq bilan to'la baza" qulayligini beradi.
Prisma Studio β vizual baza brauzeri¶
Prisma'ning eng yoqimli sovg'asi β Studio. Bitta buyruq:
Bu brauzerda (odatda http://localhost:5555) baza jadvallarini vizual ochadi: qatorlarni ko'rasiz, tahrirlaysiz, qo'shasiz, o'chirasiz β SQL yozmasdan. Ishlab chiqishda baza holatini tekshirish uchun ajoyib (productionda ishlatilmaydi). Bu β phpMyAdmin yoki DBeaver ga o'xshash, lekin to'g'ridan-to'g'ri schemangizga moslangan.
Raw query β qachon SQL ga qaytamiz?¶
ORM kuchli, lekin hamma narsa uchun emas. Murakkab analitik so'rov, baza-maxsus funksiya yoki Prisma API ifodalay olmaydigan optimizatsiya kerak bo'lsa β raw SQL ga qaytasiz. Prisma buni xavfsiz qiladi:
// Natija qaytaradigan so'rov ($queryRaw)
const stat = await prisma.$queryRaw`SELECT COUNT(*) AS soni FROM Task`;
console.log(stat); // [ { soni: 1n } ] (n β BigInt)
// Natija qaytarmaydigan amal ($executeRaw β UPDATE/DELETE)
await prisma.$executeRaw`UPDATE Task SET done = true WHERE done = false`;
Xavfsizlik: Doim tagged template (
$queryRaw`...`) ishlating β Prisma${qiymat}larni avtomatik parametr sifatida bog'laydi (SQL injection'dan himoya). Hech qachon foydalanuvchi kiritmasini matn sifatida ulamang. Agar dinamik so'rov kerak bo'lsa,$queryRawUnsafebor β lekin nomidan ko'rinib turibdiki, undan ehtiyot bo'ling.
Qoida: 95% holatda Prisma API yetadi. $queryRaw β oxirgi chora, "tashqi eshik".
REAL KEYS: Vazifa boshqaruvi (User 1:N Task) β to'liq¶
Endi hamma narsani birlashtiramiz. SQLite bilan to'liq foydalanuvchi + vazifa tizimini quramiz β bu bobdagi kodning hammasi haqiqatan ishga tushirilib tekshirilgan (Node 24.12 + Prisma 6.19).
1-qadam β schema (prisma/schema.prisma, yuqorida ko'rganimiz):
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
createdAt DateTime @default(now())
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
title String
done Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
}
2-qadam β migrate + generate:
3-qadam β CRUD + relation skripti (crud.js):
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
// toza boshlash (tartib: avval Task, keyin User β FK sababli)
await prisma.task.deleteMany();
await prisma.user.deleteMany();
// CREATE: foydalanuvchi + ichma-ich (nested) vazifalar
const ali = await prisma.user.create({
data: {
email: "ali@example.com",
name: "Ali",
tasks: {
create: [
{ title: "Prisma o'rganish" },
{ title: "Loyiha yozish" },
],
},
},
include: { tasks: true },
});
console.log("CREATE (nested):", ali.name, "->", ali.tasks.map((t) => t.title));
// CREATE: yana bir foydalanuvchi
const vali = await prisma.user.create({
data: { email: "vali@example.com", name: "Vali" },
});
console.log("CREATE:", vali.email, "id =", vali.id);
// READ: findMany + include (relation)
const hammasi = await prisma.user.findMany({
include: { tasks: true },
orderBy: { id: "asc" },
});
console.log("findMany:", hammasi.map((u) => `${u.name}(${u.tasks.length})`).join(", "));
// READ: findUnique (unique maydon bo'yicha)
const topildi = await prisma.user.findUnique({ where: { email: "ali@example.com" } });
console.log("findUnique:", topildi.name);
// READ: where + select + take/skip
const vazifalar = await prisma.task.findMany({
where: { done: false },
select: { id: true, title: true },
orderBy: { id: "asc" },
take: 1,
skip: 0,
});
console.log("findMany(where/select/take):", vazifalar);
// UPDATE
const yangilangan = await prisma.task.update({
where: { id: ali.tasks[0].id },
data: { done: true },
});
console.log("UPDATE:", yangilangan.title, "done =", yangilangan.done);
// RELATION query: foydalanuvchini bajarilgan vazifalari bilan
const aliTula = await prisma.user.findUnique({
where: { id: ali.id },
include: { tasks: { where: { done: true } } },
});
console.log("include(filtered):", aliTula.name, "bajarilgan:", aliTula.tasks.map((t) => t.title));
// TRANSACTION: ikkala sanoq birga
const [u2, t2] = await prisma.$transaction([
prisma.user.count(),
prisma.task.count(),
]);
console.log("$transaction count:", { users: u2, tasks: t2 });
// DELETE
const ochirilgan = await prisma.task.delete({ where: { id: ali.tasks[1].id } });
console.log("DELETE:", ochirilgan.title);
// raw query
const raw = await prisma.$queryRaw`SELECT COUNT(*) as soni FROM Task`;
console.log("$queryRaw:", raw);
console.log("HAMMA AMAL MUVAFFAQIYATLI");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
Ishga tushiramiz:
Haqiqiy chiqishi (biz ishga tushirdik):
CREATE (nested): Ali -> [ "Prisma o'rganish", 'Loyiha yozish' ]
CREATE: vali@example.com id = 2
findMany: Ali(2), Vali(0)
findUnique: Ali
findMany(where/select/take): [ { id: 1, title: "Prisma o'rganish" } ]
UPDATE: Prisma o'rganish done = true
include(filtered): Ali bajarilgan: [ "Prisma o'rganish" ]
$transaction count: { users: 2, tasks: 2 }
DELETE: Loyiha yozish
$queryRaw: [ { soni: 1n } ]
HAMMA AMAL MUVAFFAQIYATLI
Hammasi ishladi: nested create, include (oddiy va filtrli), findUnique, where/select/take, update (@updatedAt avtomatik), transaction, delete va raw query. Bu β bitta to'liq ORM oqimi.
Express bilan bog'lash: Bu Prisma kodini 12-bobdagi Express route'lari ichiga qo'ysangiz, to'liq REST API tayyor:
GET /users->prisma.user.findMany({ include: { tasks: true } }),POST /users->prisma.user.create(...), va hokazo. 13-bobdagi middleware bilan auth/validatsiya qo'shasiz. Prisma β backend API ning ma'lumotlar qatlami.
Xulosa¶
Bu bobda biz qo'lda SQL dan ORM ga o'tdik:
- ORM β obyekt bilan jadval orasidagi ko'prik; qo'lda SQL ni kamaytiradi, lekin abstraksiya narxi bilan keladi (murakkab so'rovda raw SQL ga qaytamiz).
- Prisma β schema-asosli, type-safe ORM.
schema.prismaβ yagona haqiqat manbai. migrate devβ schema'dan haqiqiy DB jadvallari va migration tarixi;generateβ type-safe client.- CRUD β
create/findMany/findUnique/findFirst/update/delete,where/select/orderBy/take/skipbilan. - Relation β 1:N va N:M,
@relation,includeva nested create relation larni qulay qiladi. $transaction(ikki shakl), seed, Prisma Studio,$queryRawβ kundalik amaliyot quroli.
Keyingi bobda boshqa dunyoga β NoSQL ga o'tamiz: hujjat-asosli MongoDB va uning Node ORM/ODM si Mongoose bilan tanishamiz.
Mashqlar¶
Oson¶
schema.prismaga yangiNotemodeli qo'shing:id(autoincrement),text(String),pinned(Boolean, default false).npx prisma migrate dev --name add_notebilan migration yarating vaprisma/migrationspapkasida yangi papka paydo bo'lganini ko'ring.prisma.user.createbilan ikkita foydalanuvchi yarating, so'ngfindMany({ orderBy: { name: "asc" } })bilan ularni alifbo tartibida chiqaring.- Bitta vazifa yaratib, uni
updatebilandone: trueqiling. So'ngfindUniquebilan qayta o'qib,updatedAtningcreatedAtdan kechroq ekanini tekshiring.
O'rta¶
- Bitta foydalanuvchiga nested create bilan uchta vazifa yarating. So'ng
include: { tasks: { where: { done: false } } }bilan faqat bajarilmagan vazifalarini chiqaring. takevaskipbilan sahifalash yozing: 5 ta vazifa yaratib, ikkinchi "sahifani" (har sahifada 2 ta)take: 2, skip: 2bilan oling. To'g'ri ikkita kelganini tekshiring.prisma.task.count({ where: { done: true } })vaprisma.task.count({ where: { done: false } })bilan bajarilgan/bajarilmagan vazifalar sonini hisoblang va{ done, todo }obyekti sifatida chiqaring.
Qiyin¶
- Interaktiv
$transactionyozing: callback ichida yangi User yaratsin, unga ikkita Task qo'shsin, so'ng birinchi Task nidone: trueqilsin. Callback ichida ataylab xato tashlab (throw new Error(...)), tranzaksiya rollback bo'lganini β ya'ni User ham, Task ham bazaga yozilmaganini βcountbilan tasdiqlang. $queryRawbilan analitik so'rov yozing: har bir foydalanuvchi uchun vazifalar sonini qaytaring (SELECT u.name, COUNT(t.id) ...GROUP BY). Avval Prisma API ham bu ma'lumotni bera olishini (include: { _count: { select: { tasks: true } } }) ko'rib chiqing va ikki yondashuvni solishtiring.
Yechim β 1
schema.prisma ga qo'shing:
Prisma faqat farqni hisoblaydi va prisma/migrations/<vaqt>_add_note/migration.sql ichida CREATE TABLE "Note" (...) ni yozadi. Eski init migration'i o'z joyida qoladi β tarix shu tarzda saqlanadi.
Yechim β 4
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.task.deleteMany();
await prisma.user.deleteMany();
const u = await prisma.user.create({
data: {
email: "n@x.uz",
name: "Nodir",
tasks: {
create: [
{ title: "A", done: true },
{ title: "B" },
{ title: "C" },
],
},
},
});
const natija = await prisma.user.findUnique({
where: { id: u.id },
include: { tasks: { where: { done: false } } },
});
console.log(natija.tasks.map((t) => t.title)); // [ 'B', 'C' ]
}
main().finally(() => prisma.$disconnect());
Nested create bog'langan yozuvlarni birga yaratadi; include ichidagi where esa faqat bajarilmaganlarni filtrlaydi.
Yechim β 5
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.task.deleteMany();
await prisma.user.deleteMany();
const u = await prisma.user.create({ data: { email: "p@x.uz", name: "Pagina" } });
// 5 ta vazifa
await prisma.task.createMany({
data: [1, 2, 3, 4, 5].map((n) => ({ title: `Vazifa ${n}`, userId: u.id })),
});
// 2-sahifa (har sahifada 2 ta): skip = (2-1)*2 = 2
const ikkinchiSahifa = await prisma.task.findMany({
orderBy: { id: "asc" },
take: 2,
skip: 2,
});
console.log(ikkinchiSahifa.map((t) => t.title)); // [ 'Vazifa 3', 'Vazifa 4' ]
}
main().finally(() => prisma.$disconnect());
take = sahifa hajmi (LIMIT), skip = (sahifa - 1) * hajm (OFFSET). Pagination shu ikki opsiyaga tayanadi.
Yechim β 7
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.task.deleteMany();
await prisma.user.deleteMany();
try {
await prisma.$transaction(async (tx) => {
const u = await tx.user.create({ data: { email: "t@x.uz", name: "Test" } });
await tx.task.create({ data: { title: "A", userId: u.id } });
const t2 = await tx.task.create({ data: { title: "B", userId: u.id } });
await tx.task.update({ where: { id: t2.id }, data: { done: true } });
throw new Error("ataylab xato β rollback bo'lsin"); // XATO (sinov uchun)
});
} catch (e) {
console.log("tranzaksiya bekor bo'ldi:", e.message);
}
// rollback tasdiqi: hech narsa yozilmagan bo'lishi kerak
console.log("users:", await prisma.user.count()); // 0
console.log("tasks:", await prisma.task.count()); // 0
}
main().finally(() => prisma.$disconnect());
Callback xato tashlasa, Prisma butun tranzaksiyani bekor qiladi β User ham, Task lar ham bazaga tushmaydi. count ikkalasi ham 0 ekanini ko'rsatadi. Bu β atomiklikning isboti.
Yechim β 8
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
// 1-usul: Prisma API β _count
const apiYol = await prisma.user.findMany({
select: { name: true, _count: { select: { tasks: true } } },
});
console.log("API:", apiYol.map((u) => `${u.name}: ${u._count.tasks}`));
// 2-usul: raw SQL β GROUP BY
const rawYol = await prisma.$queryRaw`
SELECT u.name, COUNT(t.id) AS soni
FROM User u
LEFT JOIN Task t ON t.userId = u.id
GROUP BY u.id
`;
console.log("RAW:", rawYol);
}
main().finally(() => prisma.$disconnect());
Ikkala usul ham bir xil natija beradi. Oddiy hisoblar uchun Prisma _count β toza va type-safe. Murakkab agregatsiya (bir nechta JOIN, oyna funksiyalari) kerak bo'lganda $queryRaw qulayroq. Tanlov β o'qishlilik bilan kuch o'rtasidagi muvozanat. Eslatma: raw natijadagi COUNT qiymati ba'zi bazalarda BigInt (1n) bo'lib qaytishi mumkin β JSON ga aylantirishdan oldin Number(...) qiling.
β¬ οΈ Oldingi: 17 β MySQL: mysql2, pool, tranzaksiya Β· π README Β· Keyingi: 19 β MongoDB va Mongoose β‘οΈ