WisarWisar
Dasturlash kitobi/8-QISM — NestJS26 daqiqa

8.3-bob: DB ulanish — TypeORM / Prisma / Mongoose bilan

8-QISM — NestJS (chuqur) · 3-mavzu


1. Kirish va motivatsiya

NestJS arxitekturasi 8.1-bob va DI 8.2-bob ni chuqur bildik. Endi NestJS'ni ma'lumotlar bazasiga ulaymiz — har real ilovaning yuragi. 6-QISMda ORM'larni (TypeORM 6.13, Prisma 6.12, Mongoose 6.2) mustaqil o'rgandik; endi ularni NestJS ichida — DI bilan integratsiyalashgan, modul tizimiga mos — ishlatamiz. Bu — karyerangizda eng ko'p ishlatadigan amaliyot (CRUD API, biznes ma'lumot).

NestJS uchchala ORM bilan ham ishlaydi, lekin har birining integratsiya naqshi boshqacha: TypeORM (@nestjs/typeormforRoot/forFeature, repository inject — dekorator asosida, NestJS bilan eng tabiiy); Mongoose (@nestjs/mongoose — schema + model inject — MongoDB uchun); Prisma (rasmiy wrapper yo'qPrismaService orqali DI — eng zamonaviy, type-safe). Tanlov — DB turi va loyihaga bog'liq: SQL + enterprise TypeORM; MongoDB Mongoose; zamonaviy/type-safe Prisma.

Asosiy g'oya: ORM'ni NestJS modul sifatida ulaysiz (forRoot — global ulanish), keyin har feature modulda repository/model/service ni inject qilasiz (DI — 8.2). Bu — 6-QISMdagi ORM bilimini NestJS DI + modul tizimi bilan birlashtiradi. Bu bob: uchchala ORM integratsiyasi (asosiy e'tibor — TypeORM va Prisma), entity/schema, repository inject, va real CRUD — chuqur.

O'xshatish: ORM — DB bilan gaplashadigan tarjimon (6-QISM). NestJS bilan integratsiya — bu tarjimonni kompaniyaga rasmiy xodim sifatida ishga olish (DI konteyner — 8.2): forRoot — tarjimonni yollash (ulanish); forFeature/@InjectRepository — har bo'limga (modul) tarjimonni biriktirish. Endi har service tarjimonni so'raydi (DI), kompaniya (NestJS) beradi. Tashkillangan, qayta ishlatiladigan.

Nega muhim?

  • Har ilova yuragi — DB'siz ilova yo'q (ma'lumot saqlash).
  • NestJS + ORM — eng ko'p ishlatadigan amaliyot (CRUD).
  • DI integratsiya — repository/model inject (toza, testlanadigan).
  • Tanlov — TypeORM/Prisma/Mongoose — loyihaga mos.

2. Nazariya — chuqur tushuntirish

2.1. NestJS DB integratsiyasi (umumiy g'oya)

text
  Uch qadam (har ORM uchun):
  1. forRoot (yoki PrismaService) — GLOBAL ulanish (bir marta — app.module)
  2. Entity/Schema — DB model (6.13, 6.2)
  3. forFeature + inject — har feature modulda repository/model (DI — 8.2)

   ORM modul sifatida (8.1: 2.13 dynamic module — forRoot)
   repository/model — DI orqali service'ga (8.2)

NestJS DB naqshi: ORM — dynamic module (forRoot — sozlanadigan ulanish — 8.1: 2.13). Ulanish bir marta (app.module), keyin har feature modul kerakli model/repository'ni oladi (forFeature + inject — DI — 8.2). Bu — 6-QISM ORM + 8-QISM DI birlashmasi.

2.2. ORM tanlovi (qaysi qachon — NestJS kontekstida)

text
  ┌──────────┬─────────────────────────┬──────────────────────┐
  │ ORM      │ Qachon                  │ NestJS integratsiya  │
  ├──────────┼─────────────────────────┼──────────────────────┤
  │ TypeORM  │ SQL + enterprise/dekorator│ @nestjs/typeorm (tabiiy)│
  │ Prisma   │ zamonaviy, type-safe    │ PrismaService (wrappersiz)│
  │ Mongoose │ MongoDB                 │ @nestjs/mongoose     │
  └──────────┴─────────────────────────┴──────────────────────┘

Tanlov (2026): TypeORM — SQL + dekorator (NestJS bilan eng tabiiy — entity dekoratorlari NestJS uslubiga mos). Prisma — zamonaviy, type-safe, tez o'syapti (2025'da Mongoose'ni o'tib ketdi). Mongoose — MongoDB (TypeORM MongoDB'da kuchsiz). Loyihaga qarab; bu bobda asosan TypeORM + Prisma.

Batafsil taqqoslash (qaysi mezon bo'yicha qaysi):

text
  ┌──────────────────┬─────────────┬──────────────┬─────────────┐
  │ Mezon            │ TypeORM     │ Prisma       │ Mongoose    │
  ├──────────────────┼─────────────┼──────────────┼─────────────┤
  │ DB turi          │ SQL (+Mongo │ SQL (Mongo   │ MongoDB     │
  │                  │ kuchsiz)    │ preview)     │ (faqat)     │
  │ Model ta'rifi    │ Entity      │ schema.prisma│ @Schema     │
  │                  │ (dekorator) │ (DSL fayl)   │ (dekorator) │
  │ Type-safety      │ Yaxshi      │ Eng kuchli   │ O'rtacha    │
  │                  │             │ (generatsiya)│             │
  │ NestJS paket     │ @nestjs/    │ yo'q         │ @nestjs/    │
  │                  │ typeorm     │ (PrismaSvc)  │ mongoose    │
  │ Migration        │ CLI+kod     │ prisma       │ qo'lda/     │
  │                  │             │ migrate      │ skript      │
  │ Query builder    │ Bor (kuchli)│ yo'q (fluent │ chaining    │
  │                  │             │ API)         │             │
  │ O'rganish        │ O'rtacha    │ Oson         │ Oson        │
  │ Qachon tanla     │ Enterprise, │ Yangi loyiha,│ MongoDB     │
  │                  │ murakkab SQL│ DX, type-safe│ hujjatli    │
  └──────────────────┴─────────────┴──────────────┴─────────────┘

Amaliy qoida: SQL DB + murakkab so'rovlar / mavjud enterprise kod TypeORM; yangi loyiha, ishlab chiquvchi qulayligi (DX) va maksimal type-safety muhim Prisma; ma'lumot modeli hujjatli (document) va MongoDB Mongoose. Uchchalasi ham production'ga yaroqli — "eng yaxshisi" yo'q, faqat loyihaga mosi bor.

2.3. TypeORM: forRoot (global ulanish)

TypeOrmModule.forRoot — DB'ga global ulanish (app.module — docs):

ts
// app.module.ts
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "postgres",                  // DB turi (6.6)
      url: process.env.DATABASE_URL,     // (8.14: ConfigService bilan — 2.4)
      entities: [User, Order],           // yoki autoLoadEntities: true
      synchronize: false,                // production'da FALSE! (6.13: 2.13)
      migrations: ["dist/migrations/*.js"],
    }),
  ],
})
export class AppModule {}

forRoot — bir marta (app.module), butun ilovaga DB ulanish (8.1: 2.13 dynamic module). synchronize: false (production — 6.13: 2.13). autoLoadEntities: true — entity'larni avtomatik (qo'lda ro'yxat o'rniga). Async sozlash — forRootAsync (ConfigService bilan — 2.4).

2.4. forRootAsync (ConfigService bilan)

ts
// Env'dan sozlash (8.14 — ConfigService DI)
TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],               // DI (8.2)
  useFactory: (config: ConfigService) => ({   // useFactory (8.2: 2.11)
    type: "postgres",
    url: config.get("DATABASE_URL"),     // env (8.14)
    synchronize: config.get("NODE_ENV") !== "production",
    autoLoadEntities: true,
  }),
}),

forRootAsync — sozlamani dinamik (ConfigService'dan — env — 8.14). useFactory (8.2: 2.11) + inject (ConfigService). Bu — to'g'ri amaliyot (kalitlar .env'da — 5.8, 14, hardcode emas).

2.4a. Connection pooling (ulanishlar hovuzi)

DB ulanishini ochish — qimmat (TCP + autentifikatsiya). Shuning uchun har ORM pool (ulanishlar hovuzi) ishlatadi: bir necha tayyor ulanishni ochib qo'yadi, so'rov kelganda pool'dan oladi, tugagach qaytaradi (6.5, 6.9). Bu — DB integratsiyasining eng ko'p e'tibordan chetda qoladigan, lekin production'da eng muhim sozlamasi.

ts
// TypeORM — pool sozlash (postgres/mysql driver'ining "extra" orqali)
TypeOrmModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    type: "postgres",
    url: config.get("DATABASE_URL"),
    autoLoadEntities: true,
    extra: {
      max: 10,                    // maksimal ulanish soni (pool hajmi)
      idleTimeoutMillis: 30000,   // bo'sh ulanish 30s'dan keyin yopiladi
      connectionTimeoutMillis: 5000,  // ulanish ololmasa 5s'da xato
    },
  }),
}),
text
  Prisma — pool DATABASE_URL query-param orqali:
  postgresql://user:parol@host:5432/db?connection_limit=10&pool_timeout=20

  Mongoose — forRoot options orqali:
  MongooseModule.forRoot(url, { maxPoolSize: 10, minPoolSize: 2 })

Pool hajmi (max/connection_limit) — muhim sozlama. Juda kichik so'rovlar navbatda kutadi (sekin). Juda katta DB'ni bosadi (Postgres default max_connections ~100; har ilova instansiyasi × pool hajmi ≤ shu chegara). Serverless (Lambda/Vercel) da har instansiya alohida pool ochadi — u yerda PgBouncer yoki Prisma Accelerate/Data Proxy ishlatiladi (ko'p instansiya bitta pool'ni bo'lishadi). Qoida: pool_hajmi × instansiya_soni < DB_max_connections 6.5-bob. Bir instansiya uchun 10–20 odatda yetadi.

2.5. TypeORM: Entity (6.13 takrori)

ts
// users/entities/user.entity.ts (6.13: 2.4)
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";

@Entity("users")
export class User {
  @PrimaryGeneratedColumn() id: number;
  @Column({ unique: true }) email: string;
  @Column({ select: false }) parol: string;        // (5.15, 14)
  @OneToMany(() => Order, (o) => o.user) orders: Order[];
}

Entity — 6.13'dagi kabi (dekorator). NestJS'da entity'lar forFeatureda ro'yxatga olinadi 2.6-bob. NestJS dekorator (8.1: 7.6) bilan tabiiy uyg'unlik (ikkalasi dekorator).

2.6. TypeORM: forFeature + InjectRepository

forFeature — feature modulda entity'larni ro'yxatga oladi; @InjectRepository — repository inject:

ts
// users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],     // bu modulda User entity
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

// users.service.ts
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User) private userRepo: Repository<User>,   // DI (8.2)
  ) {}

  hammasi() { return this.userRepo.find(); }       // TypeORM repository (6.13: 2.6)
}

forFeature + @InjectRepository — TypeORM NestJS naqshi: forFeature([User]) (modulda entity), @InjectRepository(User) (service'ga repository — DI — 8.2). Repository<User> — TypeORM repository (6.13: find/save/...). Bu — eng ko'p ishlatiladigan TypeORM+NestJS naqsh.

2.7. TypeORM: CRUD service (repository bilan)

ts
@Injectable()
export class UsersService {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}

  async hammasi(): Promise<User[]> {
    return this.repo.find();                       // (6.13: 2.6)
  }
  async bitta(id: number): Promise<User> {
    const user = await this.repo.findOne({ where: { id } });
    if (!user) throw new NotFoundException("Topilmadi");   // (8.1: 2.6 — 404)
    return user;
  }
  async yarat(dto: CreateUserDto): Promise<User> {
    const user = this.repo.create(dto);            // obyekt (6.13)
    return this.repo.save(user);                   // saqlash
  }
  async ochir(id: number): Promise<void> {
    await this.repo.delete(id);
  }
}

Repository metodlari — 6.13'dagi kabi (find/findOne/create/save/delete). NestJS qo'shadi: DI (repository inject), built-in exception (NotFoundException — 8.1). Toza qatlam (controller service repository — 8.1: 2.9).

2.7a. DataSource inject va raw SQL

Repository yetmaganda (murakkab query, raw SQL, migration'siz DDL) — DataSource to'g'ridan inject qilinadi. DataSource — TypeORM'ning markaziy ulanish obyekti (6.13: 2.2), NestJS uni DI'ga qo'yadi:

ts
import { DataSource } from "typeorm";

@Injectable()
export class HisobotService {
  constructor(private dataSource: DataSource) {}    // DI — provider avtomatik

  // Raw SQL (murakkab hisobot; parametrli — SQL injection'dan himoya — 14)
  async oylikDaromad(yil: number) {
    return this.dataSource.query(
      `SELECT date_trunc('month', created_at) AS oy, SUM(summa) AS jami
       FROM orders WHERE EXTRACT(year FROM created_at) = $1
       GROUP BY oy ORDER BY oy`,
      [yil],                        // parametr ($1) — hech qachon string konkatenatsiya emas (14)
    );
  }

  // Repository'ni DataSource orqali olish (dinamik)
  ixtiyoriyRepo() {
    return this.dataSource.getRepository(User);
  }
}

DataSource — TypeORM ulanishi (6.13: 2.2), NestJS DI'ga tayyor qo'yadi (forRootdan keyin). Ishlatiladi: raw query() (parametrli — $1/? — SQL injection'dan himoya, 14), tranzaksiya 2.13-bob, getRepository(). Repository yetsa — raw'ga o'tmang (repository/query builder xavfsizroq va typed). Raw — faqat repository imkoni yetmaganda.

2.8. Prisma: PrismaService (wrappersiz)

Prisma'da rasmiy NestJS wrapper YO'QPrismaService qo'lda yaratiladi (docs/prisma):

ts
// prisma/prisma.service.ts
import { Injectable, OnModuleInit } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {                            // lifecycle (8.1: 2.15)
    await this.$connect();                          // DB ulanish
  }
}
// PrismaClient'ni extend qiladi 6.12-bob — barcha Prisma metodlari + DI
ts
// prisma/prisma.module.ts (global — 8.1: 2.13)
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

Prisma — wrappersiz (rasmiy Prisma tavsiyasi): PrismaService extends PrismaClient 6.12-bob + OnModuleInit (ulanish — 8.1: 2.15). Global modul (har joyda). Boshqa ORM'lardan farqi: NestJS-maxsus paket yo'q, to'g'ridan Prisma Client + DI. Eng sodda, type-safe 6.12-bob.

Nega extends PrismaClient? PrismaService — PrismaClient'ning o'zi (meros orqali), shuning uchun this.user, this.$transaction, this.$queryRaw — barcha Prisma Client metodlari to'g'ridan mavjud. Ustiga NestJS lifecycle (OnModuleInit) qo'shiladi: modul ishga tushganda $connect. Bu — kompozitsiya emas, meros: bitta obyekt ham Prisma Client, ham NestJS provider (DI'ga tushadigan).

2.8a. Prisma: enableShutdownHooks (graceful shutdown)

Prisma 5+ da $connect/$disconnect uchun OnModuleInit/OnModuleDestroy yetarli (tavsiya etilgan usul). Lekin NestJS'ning enableShutdownHooks signalini (SIGINT/SIGTERM) Prisma'ga bog'lash kerak bo'lsa — main.tsda yoqiladi:

ts
// main.ts — graceful shutdown (5.10)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();        // NestJS: SIGTERM/SIGINT'da onModuleDestroy chaqiriladi
  await app.listen(3000);
}
ts
// prisma.service.ts — OnModuleDestroy Prisma ulanishini yopadi
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() { await this.$connect(); }
  async onModuleDestroy() { await this.$disconnect(); }   // shutdown'da (5.10)
}

Graceful shutdown 5.10-bob: app.enableShutdownHooks() — NestJS SIGTERM/SIGINT'ni tinglaydi va onModuleDestroy chaqiradi; u yerda $disconnect DB ulanishini toza yopadi (ochiq ulanishlar "leak" bo'lmaydi). Docker/K8s'da konteyner to'xtaganda muhim 5.10-bob. Eski Prisma (4.x) da process.on('beforeExit') ishlatilardi — Prisma 5+ da bunga hojat yo'q, onModuleDestroy yetarli.

2.9. Prisma: service'da ishlatish

ts
// users.service.ts
import { PrismaService } from "../prisma/prisma.service";

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}    // DI (8.2)

  hammasi() {
    return this.prisma.user.findMany();            // Prisma Client (6.12: 2.9)
  }
  async bitta(id: number) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) throw new NotFoundException("Topilmadi");
    return user;
  }
  yarat(dto: CreateUserDto) {
    return this.prisma.user.create({ data: dto });   // (6.12)
  }
}

Prisma service'da — this.prisma.user.findMany() (6.12: type-safe). DI orqali PrismaService inject. Eng toza, type-safe (Prisma turlari — 6.12: 2.2). NestJS + Prisma — zamonaviy yetakchi kombinatsiya.

2.10. Mongoose: forRoot + schema

Mongoose — MongoDB uchun (@nestjs/mongoose — docs):

ts
// app.module.ts
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [
    MongooseModule.forRoot(process.env.MONGO_URL),   // global ulanish (6.2: 2.3)
  ],
})
export class AppModule {}
ts
// users/schemas/user.schema.ts (NestJS dekorator uslubi)
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";

@Schema({ timestamps: true })                       // (6.2)
export class User {
  @Prop({ required: true }) ism: string;
  @Prop({ required: true, unique: true }) email: string;
}
export type UserDocument = User & Document;
export const UserSchema = SchemaFactory.createForClass(User);

Mongoose NestJS — schema dekorator bilan (@Schema, @Prop — 6.2 schema'ning NestJS uslubi). SchemaFactory.createForClass — schema yaratadi. forRoot — ulanish (6.2: 2.3). NestJS dekoratorga moslab 8.1-bob.

2.11. Mongoose: forFeature + InjectModel

ts
// users.module.ts
@Module({
  imports: [
    MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
  ],
  providers: [UsersService],
})
export class UsersModule {}

// users.service.ts
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";

@Injectable()
export class UsersService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  hammasi() { return this.userModel.find(); }      // Mongoose model (6.2: 2.6)
  yarat(dto: CreateUserDto) { return this.userModel.create(dto); }
}

forFeature + @InjectModel — Mongoose NestJS naqshi (TypeORM @InjectRepositoryga o'xshash — 2.6). Model<UserDocument> — Mongoose model (6.2: find/create/...). MongoDB loyihalarida shu naqsh.

2.12. Repository pattern (toza arxitektura — 9)

text
  Oddiy: service to'g'ridan repository/prisma ishlatadi (yetarli — kichik/o'rta)

  Toza (9: DIP — 8.2: 2.18): service interface'ga bog'liq, ORM yashirin
  Service  IUserRepository (interface)  TypeOrmUserRepository (impl)
   ORM'ni almashtirish oson (TypeORM  Prisma — service o'zgarmaydi)

Repository pattern (8.2: Misol 9, 9): katta loyihada service'ni ORM'dan ajratish (interface + DI). ORM almashtirilsa (TypeORM Prisma), service o'zgarmaydi. Lekin — ko'p loyihada to'g'ridan repository/prisma yetadi (ortiqcha abstraksiya emas). Loyiha hajmiga qarab.

2.13. Tranzaksiya (NestJS'da)

ts
// TypeORM — DataSource inject (6.13: 2.12)
@Injectable()
export class OrderService {
  constructor(private dataSource: DataSource) {}   // DI

  async yarat(dto) {
    return this.dataSource.transaction(async (manager) => {   // managed (6.13)
      const order = await manager.save(Order, dto);
      await manager.decrement(Product, { id: dto.productId }, "zaxira", 1);
      return order;
    });
  }
}

// Prisma — $transaction (6.12: 2.14)
async yarat(dto) {
  return this.prisma.$transaction(async (tx) => {
    const order = await tx.order.create({ data: dto });
    await tx.product.update({ where: { id: dto.productId }, data: { zaxira: { decrement: 1 } } });
    return order;
  });
}

Tranzaksiya — 6-QISMdagi kabi (6.13: 2.12 TypeORM DataSource, 6.12: 2.14 Prisma $transaction), lekin DI orqali (DataSource/PrismaService inject). ACID 6.9-bob — NestJS'da ham. Pul, ko'p yozuv uchun.

TypeORM: dataSource.transaction vs QueryRunner (qo'lda boshqarish). Yuqoridagi dataSource.transaction(cb)managed (avtomatik begin/commit/rollback). Ko'proq nazorat kerak bo'lsa (masalan, tranzaksiya o'rtasida shart bo'yicha savepoint, yoki bir necha repository'ni bitta ulanishga bog'lash) — QueryRunner qo'lda ishlatiladi:

ts
// TypeORM — QueryRunner (qo'lda: begin/commit/rollback — 6.13: 2.12)
@Injectable()
export class OrderService {
  constructor(private dataSource: DataSource) {}

  async yaratQoldaTx(dto: CreateOrderDto) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();               // pool'dan ulanish oladi
    await queryRunner.startTransaction();       // BEGIN
    try {
      const order = await queryRunner.manager.save(Order, dto);
      await queryRunner.manager.decrement(
        Product, { id: dto.productId }, "zaxira", 1,
      );
      await queryRunner.commitTransaction();    // COMMIT
      return order;
    } catch (err) {
      await queryRunner.rollbackTransaction();  // ROLLBACK (xato bo'lsa)
      throw err;
    } finally {
      await queryRunner.release();              // MUHIM: ulanishni pool'ga qaytar
    }
  }
}

QueryRunner — qo'lda tranzaksiya: connect startTransaction (ishlar) commitTransaction/rollbackTransaction release (finallyda — aks holda ulanish pool'da "band" qolib, oxir-oqibat pool tugaydi — pool leak). queryRunner.manager — shu tranzaksiyaga bog'langan EntityManager (barcha yozuvlar bitta ulanish/tranzaksiyada). Boshqaruv kerak bo'lmasa — dataSource.transaction(cb) soddaroq (release avtomatik). Prisma'da bunga o'xshash "interaktiv tranzaksiya" — $transaction(async (tx) => ...) (rollback avtomatik, throw bo'lsa).

Prisma: ketma-ket (batch) tranzaksiya. Bir-biriga bog'liq bo'lmagan bir necha operatsiyani atomar bajarish uchun massiv shakli ham bor:

ts
// Prisma — massiv shakli (barcha yoki hech biri — 6.12: 2.14)
await this.prisma.$transaction([
  this.prisma.user.create({ data: userDto }),
  this.prisma.profil.create({ data: profilDto }),
]);

Prisma ikki xil tranzaksiya beradi: interaktiv ($transaction(async (tx) => {...}) — o'rtada mantiq/shart bo'lsa, callback ichida tx ishlatiladi) va batch ($transaction([...]) — bog'liq bo'lmagan operatsiyalar ro'yxati, hammasi yoki hech biri). Interaktiv — kuchliroq (Misol 5), lekin biroz sekinroq (ulanish tranzaksiya davomida band).

2.14. Migration (NestJS'da)

text
  TypeORM: DataSource config + CLI (6.13: 2.13, 6.14)
  - npm run typeorm migration:generate / migration:run
  - synchronize: false (production — 6.13: 2.13)

  Prisma: prisma migrate (6.12: 2.7, 6.14)
  - npx prisma migrate dev / deploy

   Migration — 6.14 strategiyalari (expand-contract, rollback)

Migration — 6.14 (strategiyalar) NestJS'da ham amal qiladi. TypeORM CLI / Prisma migrate. Production'da synchronize: false + migration (6.13: 2.13). CI/CD'da migrate deploy (6.14: 2.14).

TypeORM CLI NestJS'da — alohida DataSource fayli. TypeORM CLI (migration:generate/run) NestJS DI'ni bilmaydi, shuning uchun CLI uchun alohida DataSource eksport qilingan fayl kerak (forRoot sozlamasidan mustaqil):

ts
// typeorm.config.ts — CLI uchun (NestJS'dan tashqarida ishlaydi)
import { DataSource } from "typeorm";
import "dotenv/config";                 // .env'ni yuklaydi (8.14)

export default new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  entities: ["dist/**/*.entity.js"],    // build'dan keyingi entity'lar
  migrations: ["dist/migrations/*.js"],
});
json
// package.json — skriptlar (typeorm-ts-node yoki build'dan keyin)
{
  "scripts": {
    "typeorm": "typeorm-ts-node-commonjs -d typeorm.config.ts",
    "migration:generate": "npm run typeorm -- migration:generate ./src/migrations/Auto",
    "migration:run": "npm run typeorm -- migration:run",
    "migration:revert": "npm run typeorm -- migration:revert"
  }
}

TypeORM migration NestJS'da: CLI DI'siz ishlagani uchun forRoot dagi sozlama CLI'ga ko'rinmaydi — shuning uchun typeorm.config.ts (DataSource eksporti) alohida yoziladi va -d bayrog'i bilan beriladi. migration:generate — entity va DB farqidan migration yaratadi 6.14-bob; migration:run — qo'llaydi; migration:revert — orqaga qaytaradi (rollback — 6.14). Production'da: synchronize: false 2.3-bob + deploy vaqtida migration:run (CI/CD — 6.14: 2.14). Prisma'da bu soddaroq: schema.prisma yagona manba, prisma migrate dev (dev) / prisma migrate deploy (prod) — alohida config kerak emas.

2.15. Testda DB (mock yoki test DB)

ts
// DI tufayli — test'da repository mock (8.2: 2.17, 8.11)
const moduleRef = await Test.createTestingModule({
  providers: [
    UsersService,
    { provide: getRepositoryToken(User), useValue: mockRepo },   // TypeORM mock
  ],
}).compile();

Test'da 8.11-bob: repository/PrismaService mock (DI — 8.2: 2.17). TypeORM — getRepositoryToken(User); Prisma — PrismaService mock. Yoki test DB (e2e — 8.11). DI — DB'ni test uchun almashtirishni oson qiladi.

2.16. Best practices (DB + NestJS — 14)

text
   forRootAsync + ConfigService (kalitlar .env — 8.14, 5.8, 14)
   synchronize: false production + migration (6.13: 2.13, 6.14)
   forFeature + inject (repository/model — DI — 2.6, 2.11)
   Service'da DB; controller yupqa (8.1: 2.7)
   Built-in exception (NotFoundException — 8.1)
   Tranzaksiya (DataSource/$transaction — 2.13)
   Maxfiy maydon (parol select: false — 5.15, 14)
   Connection pool sozlash (max/connection_limit — 2.4a)
   Graceful shutdown (enableShutdownHooks + $disconnect — 2.8a, 5.10)
   Raw SQL parametrli ($1/? — SQL injection'dan himoya — 2.7a, 14)
   ORM tanlovi loyihaga (SQLTypeORM, MongoMongoose, zamonaviyPrisma — 2.2)

3. Sintaksis — tez ma'lumotnoma

ts
// TypeORM (2.3, 2.6)
TypeOrmModule.forRoot({ type: "postgres", url, entities, synchronize: false })
TypeOrmModule.forFeature([User])           // modulda
@InjectRepository(User) private repo: Repository<User>   // inject

// TypeORM raw / tranzaksiya (2.7a, 2.13)
constructor(private dataSource: DataSource) {}
this.dataSource.query("SELECT ... WHERE x = $1", [x])   // parametrli (14)
const qr = this.dataSource.createQueryRunner(); // connect/startTransaction/commit/release

// Prisma (2.8, 2.9)
class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {}
constructor(private prisma: PrismaService) {}
this.prisma.user.findMany()
this.prisma.$transaction(async (tx) => { ... })         // interaktiv (2.13)

// Mongoose (2.10, 2.11)
MongooseModule.forRoot(url) / forFeature([{ name, schema }])
@InjectModel(User.name) private model: Model<UserDocument>

4. Batafsil kod namunalari

Misol 1 — TypeORM to'liq sozlash (forRootAsync — 2.4)

ts
// app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";   // (8.14)
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),       // (8.14)
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({     // env'dan (2.4)
        type: "postgres",
        url: config.get<string>("DATABASE_URL"),
        autoLoadEntities: true,                      // entity'lar avtomatik
        synchronize: config.get("NODE_ENV") !== "production",   // dev only (6.13: 2.13)
        logging: config.get("NODE_ENV") === "development",
      }),
    }),
    UsersModule,
  ],
})
export class AppModule {}

Misol 2 — TypeORM feature modul + service (2.6, 2.7)

ts
// users/users.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entities/user.entity";

@Module({
  imports: [TypeOrmModule.forFeature([User])],      // bu modulda User (2.6)
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}
ts
// users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./entities/user.entity";

@Injectable()
export class UsersService {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}   // DI (8.2)

  async hammasi(page = 1, limit = 20): Promise<{ data: User[]; jami: number }> {
    const [data, jami] = await this.repo.findAndCount({       // (6.13)
      skip: (page - 1) * limit, take: limit, order: { id: "DESC" },
    });
    return { data, jami };
  }

  async bitta(id: number): Promise<User> {
    const user = await this.repo.findOne({ where: { id } });
    if (!user) throw new NotFoundException(`${id}-foydalanuvchi topilmadi`);   // (8.1)
    return user;
  }

  async yarat(dto: CreateUserDto): Promise<User> {
    const mavjud = await this.repo.findOne({ where: { email: dto.email } });
    if (mavjud) throw new ConflictException("Email band");    // 409 (8.1)
    const user = this.repo.create(dto);                       // (6.13)
    return this.repo.save(user);
  }

  async yangila(id: number, dto: UpdateUserDto): Promise<User> {
    const user = await this.bitta(id);                        // 404 (qayta ishlatish)
    Object.assign(user, dto);
    return this.repo.save(user);
  }

  async ochir(id: number): Promise<void> {
    const r = await this.repo.delete(id);
    if (r.affected === 0) throw new NotFoundException("Topilmadi");
  }
}

Misol 3 — TypeORM relations + query builder (6.13, 6.7)

ts
@Injectable()
export class OrdersService {
  constructor(@InjectRepository(Order) private repo: Repository<Order>) {}

  // Relations (eager — 6.13: 2.11)
  async toliq(id: number) {
    return this.repo.findOne({
      where: { id },
      relations: { user: true, items: { product: true } },   // JOIN (6.13)
    });
  }

  // Query builder (murakkab — 6.13: 2.9)
  async statistika(userId: number) {
    return this.repo
      .createQueryBuilder("o")
      .select("COUNT(o.id)", "soni")
      .addSelect("SUM(o.summa)", "jami")
      .where("o.userId = :userId", { userId })       // parametrli (14)
      .andWhere("o.holat = :holat", { holat: "tugallandi" })
      .getRawOne();
  }
}

Misol 4 — Prisma to'liq (service, module — 2.8, 2.9)

ts
// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() { await this.$connect(); }    // ulanish (8.1: 2.15)
  async onModuleDestroy() { await this.$disconnect(); }   // graceful (5.10)
}
ts
// prisma/prisma.module.ts (global — 8.1: 2.13)
import { Global, Module } from "@nestjs/common";
import { PrismaService } from "./prisma.service";

@Global()
@Module({ providers: [PrismaService], exports: [PrismaService] })
export class PrismaModule {}
ts
// users/users.service.ts (Prisma)
@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}      // DI

  hammasi(page = 1, limit = 20) {
    return this.prisma.user.findMany({               // (6.12: 2.9)
      skip: (page - 1) * limit, take: limit,
      select: { id: true, ism: true, email: true },  // parolsiz (14)
      orderBy: { createdAt: "desc" },
    });
  }
  async bitta(id: number) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) throw new NotFoundException("Topilmadi");
    return user;
  }
  yarat(dto: CreateUserDto) {
    return this.prisma.user.create({ data: dto });
  }
}

Misol 5 — Prisma relations + tranzaksiya (6.12)

ts
@Injectable()
export class OrdersService {
  constructor(private prisma: PrismaService) {}

  // include (relations — 6.12: 2.12)
  toliq(id: number) {
    return this.prisma.order.findUnique({
      where: { id },
      include: { user: true, items: { include: { product: true } } },   // nested
    });
  }

  // Tranzaksiya (6.12: 2.14)
  async yarat(userId: number, productId: number) {
    return this.prisma.$transaction(async (tx) => {
      const product = await tx.product.findUnique({ where: { id: productId } });
      if (!product || product.zaxira < 1) throw new BadRequestException("Zaxira yo'q");
      await tx.product.update({ where: { id: productId }, data: { zaxira: { decrement: 1 } } });
      return tx.order.create({ data: { userId, summa: product.narx } });
    });
  }
}

Misol 6 — Mongoose to'liq (schema, service — 2.10, 2.11)

ts
// users/schemas/user.schema.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument } from "mongoose";

@Schema({ timestamps: true })
export class User {
  @Prop({ required: true }) ism: string;
  @Prop({ required: true, unique: true, lowercase: true }) email: string;
  @Prop({ required: true, select: false }) parol: string;   // (5.15, 14)
  @Prop({ type: String, enum: ["user", "admin"], default: "user" }) rol: string;
}
export type UserDocument = HydratedDocument<User>;
export const UserSchema = SchemaFactory.createForClass(User);
ts
// users/users.service.ts (Mongoose)
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { User, UserDocument } from "./schemas/user.schema";

@Injectable()
export class UsersService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  hammasi() { return this.userModel.find().select("-parol").lean(); }   // (6.2)
  async bitta(id: string) {
    const user = await this.userModel.findById(id);
    if (!user) throw new NotFoundException("Topilmadi");
    return user;
  }
  yarat(dto: CreateUserDto) { return this.userModel.create(dto); }
}

Misol 7 — Controller (ORM-agnostik — 8.1: 2.7)

ts
// users/users.controller.ts — ORM'dan mustaqil (controller yupqa)
@Controller("users")
export class UsersController {
  constructor(private usersService: UsersService) {}   // service'ga bog'liq (ORM emas)

  @Get()
  hammasi(@Query("page") page = "1") {
    return this.usersService.hammasi(+page);
  }
  @Get(":id")
  bitta(@Param("id") id: string) {
    return this.usersService.bitta(+id);
  }
  @Post()
  yarat(@Body() dto: CreateUserDto) {
    return this.usersService.yarat(dto);
  }
}
// Controller ORM'ni bilmaydi (service yashiradi) — TypeORM/Prisma/Mongoose almashtirsa o'zgarmaydi

Misol 8 — Repository pattern (ORM abstraksiya — 2.12, 9)

ts
// Interface (9: DIP — 8.2: 2.18)
export interface IUserRepository {
  hammasi(): Promise<User[]>;
  topId(id: number): Promise<User | null>;
  saqla(user: Partial<User>): Promise<User>;
}
export const USER_REPOSITORY = Symbol("USER_REPOSITORY");

// TypeORM implementatsiya
@Injectable()
export class TypeOrmUserRepository implements IUserRepository {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}
  hammasi() { return this.repo.find(); }
  topId(id: number) { return this.repo.findOne({ where: { id } }); }
  saqla(user: Partial<User>) { return this.repo.save(this.repo.create(user)); }
}

// Modul (almashtirsa bo'ladi — 8.2: 2.8)
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [
    UsersService,
    { provide: USER_REPOSITORY, useClass: TypeOrmUserRepository },   // ORM yashirin
  ],
})
export class UsersModule {}

// Service interface'ga bog'liq (ORM bilmaydi — 9)
@Injectable()
export class UsersService {
  constructor(@Inject(USER_REPOSITORY) private repo: IUserRepository) {}
}
// Prisma'ga o'tilsa — PrismaUserRepository yozib, useClass almashtiriladi (service o'zgarmaydi)

5. To'g'ri va noto'g'ri holatlar

1) synchronize: true production'da

text
 ma'lumot yo'qolishi (6.13: 2.13)
 false + migration (6.14)

2) Kalitlarni hardcode

ts
//  kodda (14, 5.8)
forRoot({ url: "postgres://admin:parol@..." })

//  forRootAsync + ConfigService
forRootAsync({ useFactory: (c) => ({ url: c.get("DATABASE_URL") }) })

3) forFeature unutish

text
 entity forFeature'da yo'q  "Cannot resolve repository" (2.6)
 TypeOrmModule.forFeature([User]) modulda

4) Controller'da to'g'ridan repository

ts
//  controller DB'ni biladi (qatlam buzildi — 8.1: 2.7)
@InjectRepository(User) repo  // controller'da

//  service orqali
constructor(private service: UsersService) {}

5) Parolni qaytarish

text
 parol JSON'da (14, 5.15)
 select: false (entity/schema); select bilan kerakli maydon

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Cannot resolve dependencies (Repository)

Sababi: forFeature'da entity yo'q 2.6-bob. Yechimi: TypeOrmModule.forFeature([User]) modulda.

Xato 2 — EntityMetadataNotFound

Sababi: entity forRoot'da ro'yxatda yo'q 2.3-bob. Yechimi: entities: [...] yoki autoLoadEntities: true.

Xato 3 — Prisma PrismaClient is not connected

Sababi: $connect chaqirilmagan 2.8-bob. Yechimi: OnModuleInit'da $connect.

Xato 4 — Cannot read ... of undefined (model)

Sababi: Mongoose forFeature/InjectModel nomi mos emas 2.11-bob. Yechimi: User.name izchil.

Xato 5 — Connection xatosi

Sababi: DATABASE_URL noto'g'ri, DB ishlamayapti (6.5, 6.6). Yechimi: .env tekshir; DB ishga tushir.

Xato 6 — Migration production'da xato

Sababi: synchronize: true yoki migration noto'g'ri 6.14-bob. Yechimi: false + migrate deploy (6.14: 2.14).

Xato 7 — remaining connection slots are reserved / pool tugadi

Sababi: pool juda katta yoki QueryRunner.release() chaqirilmagan (ulanish leak — 2.13), yoki ko'p instansiya × pool > DB max_connections (2.4a). Yechimi: release() ni finallyda; pool hajmini kamaytir; serverless'da PgBouncer/Accelerate (2.4a).

Xato 8 — Konteyner to'xtaganda ulanish "osilib" qoldi

Sababi: enableShutdownHooks yoqilmagan yoki onModuleDestroyda $disconnect/pool yopilmagan (2.8a). Yechimi: app.enableShutdownHooks() + onModuleDestroy (Prisma $disconnect) — 2.8a, 5.10.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • ORM'lar (6.2, 6.12, 6.13): NestJS'da DI bilan.
  • DI 8.2-bob: repository/model/PrismaService inject.
  • Dynamic module (8.1: 2.13): forRoot/forFeature.
  • Config 8.14-bob: forRootAsync + ConfigService.
  • Tranzaksiya/ACID 6.9-bob: DataSource/$transaction.
  • Migration 6.14-bob: strategiyalar.
  • Relations 8.4-bob: keyingi bob.
  • DTO/validatsiya 8.5-bob: create/update.
  • Testing 8.11-bob: repository mock.
  • Clean architecture (9): repository pattern.

8. Eng yaxshi amaliyotlar (best practices)

  • forRootAsync + ConfigService (kalitlar .env — 2.4, 14).
  • synchronize: false production + migration (6.13: 2.13, 6.14).
  • forFeature + inject (repository/model — DI — 2.6, 2.11).
  • Service'da DB; controller yupqa (qatlam — 8.1: 2.7, Misol 7).
  • Built-in exception (NotFound/Conflict — 8.1).
  • Tranzaksiya muhim amalda (DataSource/$transaction — 2.13).
  • Maxfiy maydon (parol select: false — 5.15, 14).
  • Connection pool sozlash (max/connection_limit; serverless'da PgBouncer — 2.4a).
  • Graceful shutdown (enableShutdownHooks + onModuleDestroy $disconnect — 2.8a, 5.10).
  • QueryRunner'da release har doim finallyda (pool leak'dan saqlanish — 2.13).
  • ORM tanlovi loyihaga (SQL/Mongo/zamonaviy — 2.2).
  • Repository pattern katta loyihada (ORM abstraksiya — 2.12, 9).
  • Prisma global module (PrismaService — 2.8).

9. Amaliy loyiha: "NestJS + DB CRUD API"

NestJS DB integratsiyasini mustahkamlash.

Maqsad

NestJS'ni DB'ga ulab (TypeORM yoki Prisma), to'liq CRUD API qurish: modul, entity/schema, repository inject, relations, tranzaksiya.

Talablar (requirements)

  1. DB ulanish: forRootAsync + ConfigService (TypeORM) yoki PrismaService (Misol 1, 4, 2.4, 2.8).
  2. Entity/Schema: User + Order (relations — 6.13/6.12/6.2).
  3. Feature modul: forFeature + inject (Misol 2, 2.6).
  4. CRUD service: repository/prisma; built-in exceptions; sahifalash (Misol 2, 4, 2.7).
  5. Controller: yupqa (ORM bilmaydi — Misol 7, 8.1: 2.7).
  6. Relations: eager/include (Misol 3, 5).
  7. Tranzaksiya: buyurtma + zaxira (Misol 5, 2.13).
  8. Migration: synchronize: false + migration 6.14-bob.
  9. Xavfsizlik: parol select: false; kalitlar .env (14).
  10. (Bonus) Repository pattern: interface + DI (Misol 8, 2.12).

Maslahatlar (hint)

  • forRootAsync + ConfigService (2.4, 2-xato).
  • forFeature + @InjectRepository/@InjectModel (2.6, 3-xato).
  • Controller yupqa (service orqali — 8.1: 2.7, 4-xato).
  • synchronize: false (6.13: 2.13, 1-xato).
  • Prisma: PrismaService extends PrismaClient 2.8-bob.
  • Tranzaksiya: $transaction/DataSource 2.13-bob.

"Tayyor" mezonlari (acceptance criteria)

  • DB ulanadi (env'dan — async).
  • Entity/Schema (relations).
  • Feature modul (forFeature + inject).
  • CRUD (built-in exceptions, sahifalash).
  • Controller yupqa (ORM-agnostik).
  • Relations (eager/include).
  • Tranzaksiya.
  • Migration (synchronize: false).
  • Parol select: false; kalitlar .env.
  • (Bonus) Repository pattern.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda NestJS'ni DB'ga ulashni o'rgandik:

  • Integratsiya naqshi (forRoot entity/schema forFeature + inject — 2.1); ORM tanlovi (TypeORM/Prisma/Mongoose — 2.2).
  • TypeORM (forRoot/forRootAsync — 2.3, 2.4; entity — 2.5; forFeature + @InjectRepository — 2.6; CRUD — 2.7).
  • Prisma (PrismaService wrappersiz — 2.8; global modul; service — 2.9); Mongoose (forRoot/forFeature + @InjectModel — 2.10, 2.11).
  • Tranzaksiya (DataSource/$transaction/QueryRunner — 2.13); connection pooling (2.4a); raw SQL (DataSource — 2.7a); graceful shutdown (2.8a); migration 2.14-bob; test mock 2.15-bob; repository pattern (2.12, 9).

Keyingi bob — 8.4-bob: Relations (1:1, 1:N, N:M). DB ulanishni bildik; endi NestJS'da bog'lanishlarni — one-to-one, one-to-many, many-to-many — chuqur o'rganamiz: entity relations, eager/lazy loading, cascade, va relations bilan CRUD. Bu — real ilovaning ma'lumot modeli (foydalanuvchi-buyurtma-mahsulot).


Foydalanilgan rasmiy/ishonchli manbalar

  • docs.nestjs.com/techniques/database (TypeORM), /recipes/prisma, /techniques/mongodb
  • prisma.io/docs/guides/frameworks/nestjs (PrismaService); @nestjs/typeorm, @nestjs/mongoose
  • Prisma vs TypeORM taqqoslashi (NestJS kontekstida, 2026 holati)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.3-bob: DB ulanish — TypeORM / Prisma / Mongoose bilan — Wisar