Prisma ORM + PostgreSQL — CRUD

~30 daq · NestJS · Ma'lumotlar bazasi

Prisma nima?

Prisma — yangi avlod ORM. TypeORM dan asosiy farqi: modellar TypeScript sinfida emas, alohida .prisma faylda yoziladi. Shu fayldan TypeScript tiplari avtomatik generatsiya qilinadi.

  TypeORM:                        Prisma:
  ──────────────────────────────────────────────
  user.entity.ts                  schema.prisma
  (TypeScript sinfi + dekoratorlar) (Prisma tili)
         │                               │
         ▼                               ▼
  TypeScript tip                  TypeScript tip
  (qo'lda yoziladi)               (avtomatik yaratiladi)

Prisma generatsiya qilgan tiplar — 100% baza bilan mos. Qo'lda yozilganda mos kelmaslik ehtimoli bor.


O'rnatish

Prisma 7.x da PrismaClient uchun driver adapter talab qilinadi. PostgreSQL uchun @prisma/adapter-pg va pg o'rnatiladi:

npm install prisma @prisma/adapter-pg pg
npm install -D @types/pg
npx prisma init

npx prisma init quyidagi fayllarni yaratadi:

loyiha/
├── prisma/
│   └── schema.prisma    ← modellar shu yerda yoziladi
└── .env                 ← DATABASE_URL qo'shiladi

.env ga baza manzili yoziladi:

DATABASE_URL="postgresql://postgres:12345@localhost:5432/mydb?schema=public"

tsconfig.json

NestJS 11.x va Prisma 7.x bilan ishlash uchun tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "ignoreDeprecations": "6.0",
    "esModuleInterop": true,
    "isolatedModules": true,
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2023",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": false,
    "skipLibCheck": true,
    "strictNullChecks": true,
    "forceConsistentCasingInFileNames": true,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "noFallthroughCasesInSwitch": false
  }
}

💡 ignoreDeprecations: "6.0" — Prisma 7.x da ba'zi TypeScript ogohlantirishlarini bosadi. baseUrl: "./" — generated/prisma/client import yo'lini to'g'ri ishlashi uchun kerak.


schema.prisma

// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma/client"
  // Prisma 7.x da output ko'rsatish — loyiha ildizida generated/ papkasi yaratiladi
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")
}

Asosiy atributlar:

Atribut Ma'nosi
@id Asosiy kalit
@default(autoincrement()) Avtomatik o'suvchi son
@unique Takrorlanmasin
@default(now()) Yaratilganda joriy vaqt
@updatedAt Yangilanganda avtomatik vaqt
@@map("users") Jadval nomi (standart: "User")

Migration va generatsiya

npx prisma migrate dev --name init

Bu buyruq:

  1. Schema dan migration SQL fayl yozadi
  2. Migration ni bazaga qo'llaydi
  3. TypeScript tiplarini generated/prisma/client ga generatsiya qiladi
prisma/
├── schema.prisma
└── migrations/
    └── 20240101000000_init/
        └── migration.sql     ← Prisma yozgan SQL
generated/
└── prisma/
    └── client/               ← avtomatik yaratilgan TypeScript tiplari

Prisma yozgan SQL ga o'xshab:

CREATE TABLE "users" (
    "id"        SERIAL PRIMARY KEY,
    "name"      TEXT NOT NULL,
    "email"     TEXT NOT NULL,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL
);
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");

PrismaService — NestJS ga ulash

Prisma 7.x da PrismaClient konstruktoriga adapter beriladi:

// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from 'generated/prisma/client';
// Eslatma: '@prisma/client' emas — 'generated/prisma/client'

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy
{
  constructor() {
    super({
      adapter: new PrismaPg({
        connectionString: process.env.DATABASE_URL,
      }),
    });
  }

  async onModuleInit() {
    await this.$connect();
    // NestJS moduli yuklanganda — bazaga ulanadi
  }

  async onModuleDestroy() {
    await this.$disconnect();
    // NestJS to'xtaganda — ulanishni yopadi
  }
}

Adapter nima o'zi?

Tasavvur qil: telefoning bor, lekin u European rozеtkaga ulanmaydi. Shuning uchun adapter ishlatasan — oradagi "tarjimon".

Telefon  →  Adapter  →  Rozetka
Prisma   →  Adapter  →  PostgreSQL drayveri (pg)

Prisma o'zi bazaga to'g'ridan-to'g'ri ulanishni bilmaydi. U faqat "men shu so'rovni bajarmoqchiman" deydi. Adapter esa shu so'rovni PostgreSQL ga tushunarli qilib "tarjima" qiladi va natijani qaytaradi.

Eskida Prisma ichida o'zi bitta drayver bor edi (faqat PostgreSQL uchun). Endi Prisma universal bo'ldi — MySQL, SQLite, MongoDB ham qo'llab-quvvatlanadi. Shuning uchun "qaysi baza uchun ishlaysan?" deb adapter talab qiladi.


extends PrismaClient — meros olish

export class PrismaService extends PrismaClient

extends — bu meros olish. PrismaService PrismaClient ning barcha metodlarini o'ziga oladi, ustiga NestJS ga mos qo'shimchalar bilan:

PrismaClient (ota-klass)
  - this.user.findMany()
  - this.user.create()
  - this.$connect()
  - this.$disconnect()

PrismaService (bola-klass)
  - yuqoridagi hamma narsani meros oladi
  + onModuleInit()     ← yangi qo'shildi
  + onModuleDestroy()  ← yangi qo'shildi

constructor va super() — nima bo'lyapti?

constructor() {
  super({
    adapter: new PrismaPg({
      connectionString: process.env.DATABASE_URL,
    }),
  });
}

super() — ota-klassning (PrismaClient) constructor ini chaqirish. Bola-klass ota-klassni o'zi xohlagan parametrlar bilan "yoqadi":

new PrismaService() chaqirildi
        │
        ▼
constructor() ishga tushdi
        │
        ▼
super() — ota-klass (PrismaClient) ishga tushirildi
        │
        ▼
PrismaClient ichiga adapter berildi:
  "Sen PostgreSQL bilan ishlaysan,
   manzil: DATABASE_URL"

new PrismaPg({ connectionString: ... }) — PostgreSQL adapter yaratilmoqda. process.env.DATABASE_URL esa .env faylidagi qiymatni o'qiydi:

DATABASE_URL="postgresql://postgres:12345@localhost:5432/mydb"

implements OnModuleInit, OnModuleDestroy — nima?

Bu NestJS ga aytilmoqda: "Men ikkita maxsus metodga egaman, vaqti kelganda o'zing chaqir."

NestJS ishga tushdi
        │
        ▼
onModuleInit() ← NestJS o'zi chaqiradi
  $connect() — baza ulanishi o'rnatiladi
        │
        ▼
  ... ilova ishlaydi ...
        │
        ▼
NestJS to'xtatildi (Ctrl+C)
        │
        ▼
onModuleDestroy() ← NestJS o'zi chaqiradi
  $disconnect() — ulanish yopiladi

Baza ulanishini qo'lda boshqarish kerak emas — NestJS o'zi "vaqti keldi" deb aytadi.


// src/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
// @Global() — bir marta import qilsak, barcha modullarda avtomatik mavjud
@Module({
  providers: [PrismaService],
  exports:   [PrismaService],
})
export class PrismaModule {}

@Global() — nima uchun kerak?

@Global() bo'lmasa — har bir modulda alohida import qilish kerak:

UsersModule    → PrismaModule import kerak
ProductsModule → PrismaModule import kerak
OrdersModule   → PrismaModule import kerak
...

@Global() bilan — faqat AppModule da bir marta import qilish yetarli:

AppModule → PrismaModule (bir marta)
UsersModule    → to'g'ridan-to'g'ri PrismaService ishlatsa bo'ladi
ProductsModule → to'g'ridan-to'g'ri PrismaService ishlatsa bo'ladi
OrdersModule   → to'g'ridan-to'g'ri PrismaService ishlatsa bo'ladi

// src/app.module.ts
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { UsersModule } from './users/users.module';

@Module({
  imports: [PrismaModule, UsersModule],
})
export class AppModule {}

Hammasi birgalikda qanday ishlaydi?

npx nest start
        │
        ▼
AppModule yuklandi
  → PrismaModule yuklandi (@Global — butun ilovaga tarqaldi)
    → PrismaService yaratildi
      → constructor() → PrismaClient(adapter: PrismaPg) tayyor
      → onModuleInit() → $connect() — baza ulanishi o'rnatildi
  → UsersModule yuklandi
    → UsersService yaratildi
      → PrismaService inject bo'ldi (import qilmasdan, @Global bo'lgani uchun)
        │
        ▼
GET /users → usersService.findAll() → prisma.user.findMany()
                                              │
                                              ▼
                                    PrismaPg adapter
                                              │
                                              ▼
                                    PostgreSQL — SQL bajarildi
                                              │
                                              ▼
                                    Natija qaytdi
Narsa Nima qiladi
adapter Prisma ↔ PostgreSQL orasidagi "tarjimon"
extends PrismaClient PrismaService barcha Prisma metodlarini oladi
super({ adapter }) Ota-klassga "qaysi baza, qaysi manzil" deb aytadi
onModuleInit NestJS yoqilganda baza ulanishini o'rnatadi
onModuleDestroy NestJS o'chirilganda ulanishni yopadi
@Global() Bir marta import — hamma yerda ishlaydi

DTO

// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsEmail()
  email: string;
}
// src/users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}
// PartialType — barcha maydonlarni ixtiyoriy (optional) qiladi

UsersService — CRUD

// src/users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { User } from 'generated/prisma/client';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}
  // PrismaService inject qilindi — @Global() bo'lgani uchun UsersModule ga import kerak emas

create

  async create(dto: CreateUserDto): Promise<User> {
    const exists = await this.prisma.user.findUnique({
      where: { email: dto.email },
    });
    // findUnique — @unique yoki @id maydon bo'yicha qidiradi
    // Natija: User | null

    if (exists) {
      throw new ConflictException('Bu email allaqachon mavjud');
    }

    return this.prisma.user.create({ data: dto });
  }

findAll

  async findAll(): Promise<User[]> {
    return this.prisma.user.findMany({
      orderBy: { createdAt: 'desc' },
    });
  }

findOne

  async findOne(id: number): Promise<User> {
    const user = await this.prisma.user.findUnique({
      where: { id },
    });

    if (!user) {
      throw new NotFoundException(`${id}-foydalanuvchi topilmadi`);
    }

    return user;
  }

update

  async update(id: number, dto: UpdateUserDto): Promise<User> {
    await this.findOne(id); // avval mavjudligini tekshiramiz

    return this.prisma.user.update({
      where: { id },
      data:  dto,
      // PartialType bilan mos keladi — faqat kelgan maydonlar yangilanadi
    });
  }

remove

  async remove(id: number): Promise<void> {
    await this.findOne(id);

    await this.prisma.user.delete({ where: { id } });
  }
}

UsersController

// src/users/users.controller.ts
import {
  Controller, Get, Post, Patch, Delete,
  Param, Body, ParseIntPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id);
  }

  @Patch(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateUserDto,
  ) {
    return this.usersService.update(id, dto);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id);
  }
}

UsersModule

// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  // PrismaModule @Global() — bu yerda import qilish shart emas
  controllers: [UsersController],
  providers:   [UsersService],
  exports:     [UsersService],
})
export class UsersModule {}

TypeORM ga nisbatan farq: TypeOrmModule.forFeature([User]) kerak emas — Prisma da repository tushunchasi yo'q, to'g'ridan-to'g'ri prisma.user orqali ishlaydi.


Foydali buyruqlar

# Development da migration
npx prisma migrate dev --name add_phone_to_users

# Production da migration bajarish
npx prisma migrate deploy

# Prisma Studio — brauzerda bazani ko'rish
npx prisma studio
# http://localhost:5555 da ochiladi

# Faqat TypeScript tiplarini qayta generatsiya qilish
npx prisma generate

💡 migrate dev buyrug'i prisma generate ni ham o'zi bajaradi. Alohida generate — schema o'zgarganda lekin migration kerak bo'lmaganda ishlatiladi.


Type-safe queries — Prisma ning kuchi

// TypeORM da imlo xatosi kompilyatsiyada sezilmaydi — runtime da topiladi
userRepository.findOne({ where: { emial: 'test' } });

// Prisma da — kompilyatsiyada xato beradi
this.prisma.user.findUnique({ where: { emial: 'test' } });
//                                       ^^^^^ TypeScript xato beradi:
// "Object literal may only specify known properties,
//  and 'emial' does not exist in type 'UserWhereUniqueInput'"

select — keraksiz maydonlarni yashirish

// Faqat id, name, email qaytarish
const users = await this.prisma.user.findMany({
  select: {
    id:    true,
    name:  true,
    email: true,
    // createdAt, updatedAt kelmaydi
  },
});

select ishlatilsa — faqat belgilangan maydonlar qaytadi. Parol kabi sezgir ma'lumotlarni response dan chiqarmaslik uchun qulay.


Loyiha tuzilishi

src/
├── prisma/
│   ├── prisma.service.ts    ← PrismaClient extends, adapter bilan
│   └── prisma.module.ts     ← @Global() modul
├── users/
│   ├── dto/
│   │   ├── create-user.dto.ts
│   │   └── update-user.dto.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── users.module.ts
└── app.module.ts
prisma/
├── schema.prisma
└── migrations/
generated/
└── prisma/
    └── client/              ← avtomatik yaratilgan tiplar (git ga qo'shmaslik)

⚠️ generated/ papkasini .gitignore ga qo'shing — har kimning mashinasida prisma generate bilan qayta yaratiladi.


Vazifalar

1-vazifa: schema.prisma ga Product modeli qo'shing (id, name, price Float, createdAt). Migration yarating.

2-vazifa: ProductsService yozing — create, findAll, findOne, update, remove metodlari.

3-vazifa: findAll da select qo'shing — faqat id, name, price qaytsin.


Xulosa

Prisma TypeORM analogi
schema.prisma *.entity.ts
prisma generate — (avtomatik)
prisma migrate dev migration:generate + run
prisma.user.create userRepository.save
prisma.user.findUnique userRepository.findOne
prisma.user.findMany userRepository.find
prisma.user.update userRepository.save
prisma.user.delete userRepository.remove

Prisma 7.x asosiy o'zgarishlar:

Eski (@prisma/client) Yangi (Prisma 7.x)
npm install @prisma/client @prisma/adapter-pg va pg ham kerak
new PrismaClient() new PrismaClient({ adapter: new PrismaPg(...) })
from '@prisma/client' from 'generated/prisma/client'

Keyingi dars

20-dars: Prisma — Relations (Bog'liqliklar)