Tarkibga o'tish

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-sqlite3 va mysql2 bilan INSERT, SELECT, JOIN larni 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.prisma faylining tuzilishi (datasource, generator, model, maydon turlari va atributlar @id @default @unique @relation @updatedAt), migrate dev orqali schema'dan haqiqiy DB jadvallari yasash va migration tarixi, generate orqali type-safe client. Keyin to'liq CRUD ni β€” create, findMany, findUnique, findFirst, update, delete va ularning where/select/orderBy/take/skip opsiyalarini β€” ko'ramiz. Relation (1:N va N:M), include, nested create, $transaction, seed, Prisma Studio va $queryRaw ni o'rganamiz. REAL KEYS: foydalanuvchi + vazifa (1:N) tizimini Prisma bilan to'liq quramiz. Hamma kod Node 24.12 + Prisma 6.19 + SQLite da haqiqatan migrate/generate/CRUD qilib 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 (naem o'rniga name), xato ishga tushgancha bilinmaydi β€” runtime'da undefined chiqadi.
  • Bir nechta jadvalni JOIN qilib, 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/JOIN larni 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 $queryRaw ni ko'ramiz).
  • N+1 muammosi β€” relation larni e'tiborsiz yuklasangiz, bitta so'rov o'rniga yuzta so'rov ketishi mumkin (Prisma include bu 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:

  1. Migration β€” schema'dan haqiqiy DB jadvallari (CREATE TABLE ...).
  2. Type-safe client β€” kodingiz uchun avtoto'ldirishli, turlari aniq JavaScript/TypeScript kutubxonasi.

Prisma oqimi

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/select bilan 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":

mkdir prisma-vazifa && cd prisma-vazifa
npm init -y
npm pkg set 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. Birinchisi devDependencies ga, ikkinchisi oddiy dependencies ga tushadi.

Nega @6 deb yozdik? Versiyani ataylab qadab qo'ydik. Oddiy npm install prisma bugun eng yangi Prisma 7 ni o'rnatadi β€” unda esa schema sintaksisi o'zgargan (url schema'dan prisma.config.ts ga ko'chgan, provider prisma-client bo'lgan). Agar 7-versiyada bu bobdagi aynan shu schema'ni ishlatsangiz, birinchi migrate dev da 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 @6 bilan qadadik. Bu bilan siz quyidagi har bir buyruq va chiqishni xuddi shu ko'rinishda takrorlay olasiz.

Endi loyihani ishga tayyorlaymiz:

npx prisma init --datasource-provider sqlite

Bu buyruq quyidagilarni yaratadi:

  • prisma/schema.prisma β€” asosiy schema fayli (datasource SQLite ga moslangan).
  • .env β€” maxfiy sozlamalar uchun (masalan, DB ulanish manzili).
  • .gitignore ga node_modules va .env qo'shiladi.

Versiyani tekshiramiz:

npx prisma --version
# prisma : 6.19.3
# @prisma/client : 6.19.3

Eslatma (Prisma 7+): Eng yangi Prisma 7 da sozlash biroz boshqacha (url prisma.config.ts ga ko'chgan, provider prisma-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 @6 bilan qadadik. Agar npx prisma --version sizda 6.x o'rniga 7.x ko'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 client {
  provider = "prisma-client-js"   // JavaScript/TypeScript client yasaydi
}

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:

npx prisma migrate dev --name init

--name init β€” bu migration'ga nom beradi (xuddi git commit xabari kabi). Buyruq nima qiladi:

  1. Schema'ni hozirgi baza holati bilan solishtiradi.
  2. Farqni amalga oshiradigan SQL faylini yozadi (prisma/migrations/.../migration.sql).
  3. Bu SQL ni bazaga qo'llaydi (jadvallarni yaratadi).
  4. So'ng avtomatik prisma generate ni 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 dev vs migrate deploy: migrate dev β€” ishlab chiqish uchun (yangi migration yaratadi). Production'da esa prisma migrate deploy ishlatiladi β€” u faqat mavjud migration'larni qo'llaydi, yangi yaratmaydi. CI/deploy haqida ../git-github/README.md ga 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):

npx prisma generate

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 PrismaClient nusxasini yarating. Har so'rovda yangi new 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.userId User ga ishora qiladi (foreign key). Shuning uchun User ni o'chirishdan oldin uning Task larini o'chirish kerak (yoki schema'da onDelete: Cascade qo'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.

Relation 1:N

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(...) va tasks 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:

{
  "prisma": { "seed": "node prisma/seed.js" }
}

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:

npx prisma studio

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, $queryRawUnsafe bor β€” 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:

npx prisma migrate dev --name init

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:

node crud.js

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/skip bilan.
  • Relation β€” 1:N va N:M, @relation, include va 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

  1. schema.prisma ga yangi Note modeli qo'shing: id (autoincrement), text (String), pinned (Boolean, default false). npx prisma migrate dev --name add_note bilan migration yarating va prisma/migrations papkasida yangi papka paydo bo'lganini ko'ring.
  2. prisma.user.create bilan ikkita foydalanuvchi yarating, so'ng findMany({ orderBy: { name: "asc" } }) bilan ularni alifbo tartibida chiqaring.
  3. Bitta vazifa yaratib, uni update bilan done: true qiling. So'ng findUnique bilan qayta o'qib, updatedAt ning createdAt dan kechroq ekanini tekshiring.

O'rta

  1. Bitta foydalanuvchiga nested create bilan uchta vazifa yarating. So'ng include: { tasks: { where: { done: false } } } bilan faqat bajarilmagan vazifalarini chiqaring.
  2. take va skip bilan sahifalash yozing: 5 ta vazifa yaratib, ikkinchi "sahifani" (har sahifada 2 ta) take: 2, skip: 2 bilan oling. To'g'ri ikkita kelganini tekshiring.
  3. prisma.task.count({ where: { done: true } }) va prisma.task.count({ where: { done: false } }) bilan bajarilgan/bajarilmagan vazifalar sonini hisoblang va { done, todo } obyekti sifatida chiqaring.

Qiyin

  1. Interaktiv $transaction yozing: callback ichida yangi User yaratsin, unga ikkita Task qo'shsin, so'ng birinchi Task ni done: true qilsin. Callback ichida ataylab xato tashlab (throw new Error(...)), tranzaksiya rollback bo'lganini β€” ya'ni User ham, Task ham bazaga yozilmaganini β€” count bilan tasdiqlang.
  2. $queryRaw bilan 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:

model Note {
  id     Int     @id @default(autoincrement())
  text   String
  pinned Boolean @default(false)
}
npx prisma migrate dev --name add_note

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 ➑️