WisarWisar
Dasturlash kitobi/8-QISM — NestJS26 daqiqa

8.4-bob: Relations (1:1, 1:N, N:M)

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


1. Kirish va motivatsiya

NestJS'ni DB'ga uladik 8.3-bob. Endi real ilovaning ma'lumot modelining yuragiga — bog'lanishlarga (relations) — o'tamiz. Hech bir jiddiy ilova bitta jadval emas: foydalanuvchi va uning buyurtmalari, buyurtma va mahsulotlar, mahsulot va kategoriya, post va kommentlar. Bu bog'lanishlarni to'g'ri modellashtirish — butun ilova arxitekturasining poydevori. 6-QISMda relations'ni nazariy ko'rdik (6.1: cardinality, 6.13: TypeORM relations); endi NestJS'da — to'liq, amaliy, CRUD bilan — o'rganamiz.

Uch xil bog'lanish bor (6.1: 2.10 — cardinality): One-to-One (1:1 — foydalanuvchi profil); One-to-Many / Many-to-One (1:N — foydalanuvchi ko'p buyurtma); Many-to-Many (N:M — mahsulot buyurtma, talaba kurs). TypeORM bularni dekorator bilan ifodalaydi (@OneToOne, @OneToMany/@ManyToOne, @ManyToMany) — NestJS uslubiga tabiiy mos. Bu bob asosan TypeORM ga qaratiladi (NestJS bilan eng tabiiy — 8.3: 2.2), Prisma/Mongoose eslatmalari bilan.

Bog'lanishlarda eng muhim nuqtalar: owning side (FK qaysi jadvalda), eager vs lazy loading (bog'liq ma'lumot qachon yuklanadi), cascade (asosiy bilan birga saqlash/o'chirish), va N+1 muammosi (6.10: 2.11 — eng ko'p performance xatosi). Bu bob: uch bog'lanish turi, JoinColumn/JoinTable, eager/lazy/cascade, relations bilan CRUD, va N+1 — chuqur. Bu — real ma'lumot modeli (e-commerce: user-order-product).

O'xshatish: relations — oilaviy daraxt va aloqalar. 1:1 — er-xotin (har birida bitta juft). 1:N — ona va bolalari (bir ona — ko'p bola; har bola — bir ona). N:M — talabalar va kurslar (talaba ko'p kursga; kurs ko'p talabaga — o'rtada "ro'yxat" — junction jadval — 6.1: 2.11). To'g'ri aloqani modellashtirsangiz — ma'lumot tartibli, izlash oson; noto'g'ri — chalkashlik, takror.

Nega muhim?

  • Real ma'lumot modeli — har ilova bog'langan ma'lumotlardan (user-order).
  • Arxitektura poydevori — relations to'g'ri bo'lsa, ilova mustahkam.
  • Performance — eager/lazy/N+1 — tezlik 6.10-bob.
  • CRUD — relations bilan yaratish/o'qish (nested).

2. Nazariya — chuqur tushuntirish

2.1. Relations va cardinality (6.1 takrori)

text
  One-to-One (1:1):   User  Profile (har user — bitta profil)
  One-to-Many (1:N):  User  Order (bir user — ko'p buyurtma)
  Many-to-One (N:1):  Order  User (1:N ning teskari ko'rinishi)
  Many-to-Many (N:M): Product  Order (junction jadval — 6.1: 2.11)

Cardinality (6.1: 2.10) — bog'lanishdagi son (nechta-nechta). 1:N — eng ko'p (FK "ko'p" tomonda). N:M — junction jadval (TypeORM avtomatik). NestJS'da bular dekorator bilan 2.2-bob. Avval ma'lumot modelini o'ylang (ER — 6.15), keyin kodlang.

2.2. TypeORM relation dekoratorlari

text
  @OneToOne + @JoinColumn      — 1:1 (JoinColumn — FK egasi)
  @OneToMany + @ManyToOne      — 1:N (juft! @ManyToOne'da FK)
  @ManyToMany + @JoinTable     — N:M (JoinTable — junction egasi)

Relation dekoratorlari (6.13: 2.10): har bog'lanish ikki tomonda ta'riflanadi (@OneToMany bir tomonda, @ManyToOne boshqasida). @JoinColumn/@JoinTableowning side (FK/junction qaysi tomonda — 2.4). NestJS dekorator 8.1-bob bilan tabiiy.

2.3. One-to-Many / Many-to-One (eng ko'p)

ts
// User  ko'p Order (1:N)
@Entity()
export class User {
  @PrimaryGeneratedColumn() id: number;
  @OneToMany(() => Order, (order) => order.user)   // bir user — ko'p order
  orders: Order[];
}

@Entity()
export class Order {
  @PrimaryGeneratedColumn() id: number;
  @ManyToOne(() => User, (user) => user.orders, { onDelete: "CASCADE" })   // ko'p order — bir user
  @JoinColumn({ name: "user_id" })                  // FK ustun (owning side — 2.4)
  user: User;
}

1:N / N:1 — eng keng tarqalgan 6.1-bob. @OneToMany (User tomoni — massiv); @ManyToOne (Order tomoni — FK shu yerda, owning side). onDelete: "CASCADE" (user o'chsa — buyurtmalari ham — 6.6: 2.7). FK "ko'p" tomonda (Order.user_id) — relatsion qoida.

2.4. Owning side (FK qaysi tomonda — muhim)

text
  Owning side — FK/junction qaysi jadvalda saqlanadi:

  1:N  @ManyToOne tomoni OWNING (FK "ko'p" tomonda — Order.user_id)
  1:1  @JoinColumn qaysi tomonda — o'sha OWNING
  N:M  @JoinTable qaysi tomonda — o'sha OWNING (junction egasi)

   Faqat owning side DB'ga FK yozadi; boshqasi — "inverse" (faqat o'qish)

Owning side — bog'lanishni kim boshqaradi (FK qayerda). 1:N'da — @ManyToOne (FK "ko'p" tomonda — tabiiy). 1:1/N:M'da — @JoinColumn/@JoinTable qaysi tomonda. Inverse side (@OneToMany) — faqat navigatsiya (o'qish). Bu — qaysi tomondan saqlashni belgilaydi.

2.5. One-to-One (1:1)

ts
// User  Profile (1:1)
@Entity()
export class User {
  @PrimaryGeneratedColumn() id: number;
  @OneToOne(() => Profile, (profile) => profile.user, { cascade: true })
  @JoinColumn()                                     // FK User'da (owning — 2.4)
  profile: Profile;
}

@Entity()
export class Profile {
  @PrimaryGeneratedColumn() id: number;
  @Column() bio: string;
  @OneToOne(() => User, (user) => user.profile)     // inverse (FK yo'q)
  user: User;
}

1:1 — har birida bitta juft (user profil). @OneToOne + @JoinColumn (FK egasi — 2.4). Misol: user-profil, user-sozlamalar, buyurtma-to'lov. cascade: true — user bilan profil ham saqlanadi 2.7-bob. Kamroq, lekin aniq holatlarda.

2.6. Many-to-Many (N:M)

ts
// Product  Order (N:M — junction avtomatik)
@Entity()
export class Order {
  @PrimaryGeneratedColumn() id: number;
  @ManyToMany(() => Product)
  @JoinTable({ name: "order_products" })            // junction jadval (owning — 2.4)
  products: Product[];
}

@Entity()
export class Product {
  @PrimaryGeneratedColumn() id: number;
  @ManyToMany(() => Order, (order) => order.products)   // inverse
  orders: Order[];
}

N:M@ManyToMany + @JoinTable (junction jadval — TypeORM avtomatik yaratadi — 6.1: 2.11). Misol: mahsulot-buyurtma, post-teg, talaba-kurs. Qo'shimcha maydon kerak bo'lsa (miqdor, narx — buyurtmadagi mahsulot), junction'ni alohida entity qiling 2.7-bob — chunki @JoinTable faqat ID bog'laydi.

2.7. Junction entity (qo'shimcha maydon bilan)

ts
// OrderItem — junction + qo'shimcha maydon (miqdor, narx — snapshot — 6.3)
@Entity()
export class OrderItem {
  @PrimaryGeneratedColumn() id: number;
  @ManyToOne(() => Order, (o) => o.items) order: Order;       // N:M  ikki 1:N
  @ManyToOne(() => Product) product: Product;
  @Column() miqdor: number;
  @Column({ type: "decimal" }) narx: number;                  // snapshot (6.3: Misol 7)
}

@Entity()
export class Order {
  @OneToMany(() => OrderItem, (item) => item.order, { cascade: true })
  items: OrderItem[];                                          // junction entity
}

Junction entity (N:M + qo'shimcha maydon): toza @ManyToMany faqat ID bog'laydi (miqdor/narx saqlay olmaydi). Buyurtmadagi mahsulot — miqdor, narx (snapshot — 6.3) kerak OrderItem alohida entity (N:M = ikki 1:N). Bu — real e-commerce naqshi (6.1: 2.11).

2.8. Eager loading (avtomatik yuklash)

ts
// Eager — relation HAR DOIM birga yuklanadi
@Entity()
export class Order {
  @ManyToOne(() => User, { eager: true })           // har Order so'rovida User ham
  user: User;
}
// orderRepo.find()  har order'da user avtomatik (alohida so'ramasdan)

Eager loading ehtiyot: eager: true — relation har doim yuklanadi (qulay, lekin ortiqcha ma'lumot — har so'rovda user'ni olib keladi, kerak bo'lmasa ham). Kichik, doim-kerakli relation uchun mayli; aks holda lazy yoki relations opsiya 2.10-bob afzal. Eager — barcha so'rovga ta'sir qiladi.

2.9. Lazy loading (kerak bo'lganda)

ts
// Lazy — Promise (kerak bo'lganda yuklanadi)
@Entity()
export class User {
  @OneToMany(() => Order, (o) => o.user, { lazy: true })
  orders: Promise<Order[]>;                          // Promise!
}
// const orders = await user.orders;   // shu yerda yuklanadi

Lazy loading — relation Promise (kerak bo'lganda await bilan yuklanadi). Foydali (kerakmas yuklamaydi), lekin N+1 xavfi (2.12 — tsiklda har element uchun alohida so'rov). Eager/lazy o'rniga ko'pincha relations opsiya 2.10-bob afzal (aniq, so'rovga qarab).

2.10. relations opsiyasi (eng yaxshi — so'rovga qarab)

ts
// relations opsiya — so'rovda aniq belgilash (eager/lazy o'rniga — afzal)
@Injectable()
export class OrdersService {
  constructor(@InjectRepository(Order) private repo: Repository<Order>) {}

  toliq(id: number) {
    return this.repo.findOne({
      where: { id },
      relations: { user: true, items: { product: true } },   // aniq (nested — JOIN)
    });
  }
  // user kerak bo'lmagan so'rovda relations bermaysiz (tez)
}

relations opsiya — eng yaxshi yondashuv (6.13: 2.11): har so'rovda kerakli relation'ni aniq belgilaysiz (eager — har doim; lazy — N+1 xavfi). relations: { user: true } — JOIN (bir so'rovda). Ichma-ich (items: { product: true }). Bu — performance va aniqlikni beradi (N+1 oldini oladi — 2.12).

2.11. Cascade (asosiy bilan birga)

ts
// Cascade — asosiy entity bilan relation'ni ham saqlash/o'chirish
@Entity()
export class Order {
  @OneToMany(() => OrderItem, (item) => item.order, {
    cascade: true,                                   // order saqlansa, items ham
  })
  items: OrderItem[];
}
// orderRepo.save(order)  order.items ham avtomatik saqlanadi (alohida emas)

Cascade (TypeORM) — asosiy bilan relation'ni birga saqlash (insert/update). cascade: truesave(order) order + items'ni birga saqlaydi (nested write — 6.12: 2.13 ruhida). cascade (TypeORM darajasi — save) vs onDelete: CASCADE (DB darajasi — o'chirish — 6.6: 2.7) — farqli! Ehtiyot bilan (ortiqcha cascade — kutilmagan yozish).

2.12. N+1 muammosi (eng muhim performance — 6.10)

text
   N+1 (lazy yoki tsiklda alohida so'rov — 6.10: 2.11):
  const orders = await orderRepo.find();           // 1 so'rov
  for (const o of orders) {
    const user = await o.user;                      // lazy  har order uchun (N so'rov!)
  }
   1 + N so'rov (100 order  101 so'rov — sekin!)

   relations (JOIN — bir so'rov):
  const orders = await orderRepo.find({ relations: { user: true } });   // 1 so'rov

N+1 — NestJS+TypeORM'da eng ko'p performance xatosi (6.10: 2.11): lazy loading yoki tsikldagi alohida so'rov 1+N so'rov. relations opsiya (JOIN — bir so'rov) bilan oldini oling. Murakkab holatda — DataLoader (so'rovlarni guruhlaydi — batching). EXPLAIN/log bilan tekshiring (6.10: 2.9).

2.13. Relations bilan CRUD (nested)

text
  Create (cascade — 2.11):
  order = { user, items: [...] }  save (hammasi birga)

  Read (relations — 2.10):
  findOne({ relations: { user, items } })  bog'liq bilan

  Update: relation'ni qayta saqlash (ehtiyot — eski/yangi)
  Delete: onDelete CASCADE (DB) yoki cascade (TypeORM)

Relations bilan CRUD: create — cascade (nested birga — 2.11); read — relations opsiya 2.10-bob; update — relation'ni ehtiyot (eski item'lar?); delete — onDelete 6.6-bob. DTO orqali 8.5-bob — raw entity emas (best practices).

2.14. Prisma va Mongoose relations (qisqacha)

text
  Prisma (6.12: 2.6):
  - schema.prisma'da @relation; include bilan yuklash (6.12: 2.12)
  - N:M — avtomatik junction; nested write (6.12: 2.13)
  - eng type-safe relations

  Mongoose (6.2, 6.3):
  - reference (ObjectId + ref) + populate (6.2: 2.13)
  - yoki embed (birga — 6.3: 2.11)
  - N:M — ID massivlari (6.3: 2.14)

Prisma relations 6.12-bob@relation + include (type-safe, junction avtomatik). Mongoose (6.2, 6.3) — reference + populate yoki embed (NoSQL — qachon qaysi biri — 6.3: 2.13). Bu bob TypeORM'ga qaratilgan; Prisma/Mongoose — 6-QISMda chuqur (NestJS'da bir xil g'oya).

2.15. Bidirectional vs unidirectional (ikki tomonlama yoki bir tomonlama)

ts
// Unidirectional (bir tomonlama) — faqat bir tomonda dekorator
@Entity()
export class Order {
  @ManyToOne(() => User)                             // inverse funksiya YO'Q
  @JoinColumn({ name: "user_id" })
  user: User;                                        // order.user bor; user.orders YO'Q
}
// User entity'da orders massivi umuman ta'riflanmaydi

// Bidirectional (ikki tomonlama) — ikkala tomonda ham, inverse funksiya bilan
@Entity()
export class User {
  @OneToMany(() => Order, (order) => order.user)     // inverse funksiya — bog'lovchi
  orders: Order[];                                   // user.orders BOR
}
@Entity()
export class Order {
  @ManyToOne(() => User, (user) => user.orders)      // ikkinchi argument — inverse
  @JoinColumn({ name: "user_id" })
  user: User;                                        // order.user ham BOR
}

Unidirectional — bog'lanish faqat bir tomondan ko'rinadi (order.user bor, lekin user.orders yo'q); ikkinchi (inverse) argument ((user) => user.orders) yozilmaydi. Bidirectionalikkala tomondan navigatsiya mumkin (order.user va user.orders), har tomon inverse funksiya bilan bog'lanadi. Muhim: ikkala shakl ham DB'da bir xil — bitta FK (Order.user_id); bidirectional qo'shimcha jadval yaratmaydi, faqat TypeScript darajasida ikki tomonlama navigatsiya beradi. Qoida: teskari yo'nalish (user.orders) kerak bo'lsa — bidirectional; aks holda unidirectional (soddaroq, kod ozroq). Inverse funksiyani yozganda ikkala tomonni ham to'g'ri bog'lang, aks holda TypeORM aloqani noto'g'ri tushunadi.

2.16. onDelete variantlari (CASCADE / SET NULL / RESTRICT / NO ACTION)

ts
// FK o'chirilganda DB nima qiladi — @ManyToOne opsiyasida (6.6: 2.7)
@Entity()
export class Order {
  // 1) CASCADE — user o'chsa, uning barcha order'lari ham o'chadi
  @ManyToOne(() => User, (u) => u.orders, { onDelete: "CASCADE" })
  user: User;

  // 2) SET NULL — user o'chsa, order.user_id NULL bo'ladi (order qoladi, egasiz)
  @ManyToOne(() => User, (u) => u.orders, { onDelete: "SET NULL", nullable: true })
  user: User | null;                                 // ustun nullable bo'lishi SHART

  // 3) RESTRICT — user'da order bo'lsa, uni o'chirishga DB YO'L QO'YMAYDI (xato)
  @ManyToOne(() => User, (u) => u.orders, { onDelete: "RESTRICT" })
  user: User;

  // 4) NO ACTION — RESTRICT'ga o'xshash (tekshiruv tranzaksiya oxirida — standart)
}

onDelete — bog'langan qator (parent) o'chirilganda DB darajasida (FK constraint) nima bo'lishini belgilaydi (6.6: 2.7). CASCADE — bola qatorlar ham o'chadi (user order'lari). SET NULL — FK ustun NULL bo'ladi (bola qoladi, egasi yo'qoladi — ustun nullable: true bo'lishi shart, aks holda migratsiya xato beradi). RESTRICT / NO ACTION — bola mavjud bo'lsa, parent'ni o'chirishga ruxsat bermaydi (himoya — masalan buyurtmasi bor foydalanuvchini adashib o'chirmaslik). Bu — DB darajasi (onDelete); TypeORM darajasidagi cascade 2.11-bobboshqa narsa (save uchun). Qaysi birini tanlash — biznes qoidasiga bog'liq: audit/tarix kerak bo'lsa RESTRICT yoki soft delete (Misol 8), tozalash kerak bo'lsa CASCADE.

2.17. Self-relation (o'ziga bog'lanish — daraxt/ierarxiya)

ts
// Category  Category (kategoriya  subkategoriya — o'ziga 1:N)
@Entity()
export class Category {
  @PrimaryGeneratedColumn() id: number;
  @Column() nom: string;

  @ManyToOne(() => Category, (c) => c.children, { onDelete: "SET NULL", nullable: true })
  @JoinColumn({ name: "parent_id" })
  parent: Category | null;                           // yuqoridagi kategoriya (root'da NULL)

  @OneToMany(() => Category, (c) => c.parent)
  children: Category[];                              // pastdagi subkategoriyalar
}
// Comment  Comment (izohga javob — thread) ham xuddi shu naqsh:
// parent: Comment (javob berilgan izoh) + replies: Comment[]

Self-relation — entity o'ziga bog'lanadi (bir jadval ichida ierarxiya). Misollar: kategoriya-daraxti (parent/children), izoh-javoblari (comment thread), xodim-boshliq (manager). 1:N self-relation'da FK shu jadvalda (parent_id) — root elementda NULL. Chuqur daraxtni yuklashda N+1/rekursiya xavfi 2.12-bob — bir necha bosqichni relations: { children: { children: true } } bilan aniq belgilang, cheksiz chuqurlik uchun esa @tree (TypeORM Tree Entity — closure-table/materialized-path) yoki rekursiv CTE 6.7-bob ishlating. Bidirectional 2.15-bob bu yerda tabiiy — parent ham, children ham kerak.

2.18. DataLoader — N+1'ni batching bilan yechish (GraphQL/murakkab)

ts
// DataLoader — bir "tick" ichidagi ko'p so'rovni BITTA so'rovga guruhlaydi (batching)
import DataLoader from "dataloader";

@Injectable({ scope: Scope.REQUEST })                // har so'rovga alohida (kesh toza)
export class UsersLoader {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}

  private loader = new DataLoader<number, User>(async (ids: readonly number[]) => {
    // ids: [1, 5, 9, ...] — barcha kerakli user'lar BIR so'rovda (IN)
    const users = await this.repo.findBy({ id: In([...ids]) });
    const map = new Map(users.map((u) => [u.id, u]));
    return ids.map((id) => map.get(id)!);            // tartib ids bilan bir xil bo'lishi SHART
  });

  load(id: number) {
    return this.loader.load(id);                     // ko'p chaqiruv — bitta batch
  }
}
// GraphQL resolver'da: order.user  usersLoader.load(order.userId)
// 100 order  user'lar 1 so'rovda (N+1 emas!)

DataLoader — N+1'ni 2.12-bob hal qilishning umumiy usuli, ayniqsa GraphQL (8-QISM: 17-bob) resolver'larida (u yerda relations opsiya har doim mos kelmaydi). Ishlash tamoyili: bir event-loop "tick" ichida yig'ilgan barcha load(id) chaqiruvlarini bitta batch funksiyaga to'playdi va bitta IN (...) so'rov qiladi. Ikki qoida: (1) batch funksiya natijasi kirish ids bilan bir xil tartibda bo'lishi shart; (2) DataLoader request-scoped (Scope.REQUEST) bo'lsin — aks holda keshi so'rovlar orasida "oqib ketadi" (eski ma'lumot). Oddiy REST'da relations/JOIN yetarli; DataLoader — murakkab graf yoki GraphQL uchun.

2.19. Relations dizayni (best practices — 6.15)

text
   ER modeling 6.15-bob — relations'ni boshidan o'ylang
   Owning side to'g'ri (FK joyi — 2.4)
   relations opsiya (eager/lazy o'rniga — aniq — 2.10)
   N+1'dan saqlaning (relations/JOIN — 2.12)
   N:M + qo'shimcha maydon  junction entity 2.7-bob
   onDelete to'g'ri (CASCADE/SET NULL/RESTRICT — biznes qoidasi — 2.16)
   onDelete (DB) vs cascade (TypeORM) farqini biling 2.11-bob
   Bidirectional faqat kerak bo'lsa (aks holda unidirectional — 2.15)
   Self-relation daraxti — chuqurlikni cheklab yuklang 2.17-bob
   Snapshot (buyurtma narxi — 6.3) — junction'da
   DTO orqali (raw entity emas — 8.5)

3. Sintaksis — tez ma'lumotnoma

ts
// 1:N / N:1 (2.3)
@OneToMany(() => Order, (o) => o.user) orders: Order[];           // User
@ManyToOne(() => User, (u) => u.orders) @JoinColumn() user: User; // Order (owning)

// 1:1 (2.5)
@OneToOne(() => Profile) @JoinColumn() profile: Profile;

// N:M (2.6)
@ManyToMany(() => Product) @JoinTable() products: Product[];

// Loading (2.8-2.10)
{ eager: true } / { lazy: true } / findOne({ relations: { user: true } })

// Cascade 2.11-bob: { cascade: true }; { onDelete: "CASCADE" }

// onDelete variantlari (2.16)
{ onDelete: "CASCADE" }    // bola ham o'chadi
{ onDelete: "SET NULL" }   // FK NULL (nullable shart)
{ onDelete: "RESTRICT" }   // bola bor bo'lsa o'chirmaydi

// Self-relation 2.17-bob: @ManyToOne(() => C) parent; @OneToMany(() => C) children;

// QueryBuilder JOIN (Misol 4): .leftJoinAndSelect("o.items", "item")

4. Batafsil kod namunalari

Misol 1 — To'liq e-commerce entity'lar (uch relation — 2.3, 2.5, 2.7)

ts
// user.entity.ts
@Entity("users")
export class User {
  @PrimaryGeneratedColumn() id: number;
  @Column({ unique: true }) email: string;

  @OneToOne(() => Profile, (p) => p.user, { cascade: true })   // 1:1 (2.5)
  @JoinColumn() profile: Profile;

  @OneToMany(() => Order, (o) => o.user)                       // 1:N (2.3)
  orders: Order[];
}

// profile.entity.ts
@Entity("profiles")
export class Profile {
  @PrimaryGeneratedColumn() id: number;
  @Column({ nullable: true }) bio: string;
  @OneToOne(() => User, (u) => u.profile) user: User;          // inverse
}

// order.entity.ts
@Entity("orders")
export class Order {
  @PrimaryGeneratedColumn() id: number;
  @Column({ type: "decimal", precision: 10, scale: 2 }) summa: number;

  @ManyToOne(() => User, (u) => u.orders, { onDelete: "CASCADE" })   // N:1 (2.3, 2.4)
  @JoinColumn({ name: "user_id" }) user: User;

  @OneToMany(() => OrderItem, (item) => item.order, { cascade: true })   // junction (2.7)
  items: OrderItem[];
}

// order-item.entity.ts (junction + qo'shimcha — 2.7)
@Entity("order_items")
export class OrderItem {
  @PrimaryGeneratedColumn() id: number;
  @ManyToOne(() => Order, (o) => o.items) order: Order;
  @ManyToOne(() => Product, { eager: true }) product: Product;       // eager (kichik — 2.8)
  @Column() miqdor: number;
  @Column({ type: "decimal" }) narx: number;                          // snapshot (6.3)
}

// product.entity.ts
@Entity("products")
export class Product {
  @PrimaryGeneratedColumn() id: number;
  @Column() nom: string;
  @Column({ type: "decimal" }) narx: number;
  @ManyToOne(() => Category, (c) => c.products) category: Category;   // N:1
}

Misol 2 — Relations bilan o'qish (relations opsiya — 2.10)

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

  // To'liq buyurtma (nested relations — JOIN — 2.10)
  async toliq(id: number): Promise<Order> {
    const order = await this.repo.findOne({
      where: { id },
      relations: {
        user: { profile: true },                     // user + profil (ichma-ich)
        items: { product: { category: true } },      // items + product + category
      },
    });
    if (!order) throw new NotFoundException("Topilmadi");
    return order;
  }

  // Foydalanuvchi buyurtmalari (kerakli relation'lar)
  async userBuyurtmalari(userId: number): Promise<Order[]> {
    return this.repo.find({
      where: { user: { id: userId } },               // relation bo'yicha filtr
      relations: { items: { product: true } },
      order: { id: "DESC" },
    });
  }
}

Misol 3 — Relations bilan yaratish (cascade — 2.11, 2.13)

ts
@Injectable()
export class OrdersService {
  constructor(
    @InjectRepository(Order) private repo: Repository<Order>,
    @InjectRepository(Product) private productRepo: Repository<Product>,
    private dataSource: DataSource,
  ) {}

  // Buyurtma + items birga (tranzaksiya + cascade — 6.13: 2.12)
  async yarat(userId: number, dto: CreateOrderDto): Promise<Order> {
    return this.dataSource.transaction(async (manager) => {
      const items: OrderItem[] = [];
      let summa = 0;

      for (const item of dto.items) {
        const product = await manager.findOne(Product, { where: { id: item.productId } });
        if (!product || product.zaxira < item.miqdor) {
          throw new BadRequestException(`${product?.nom}: zaxira yetarli emas`);   // (8.1)
        }
        product.zaxira -= item.miqdor;
        await manager.save(product);

        const orderItem = manager.create(OrderItem, {
          product, miqdor: item.miqdor, narx: product.narx,   // snapshot (6.3)
        });
        items.push(orderItem);
        summa += product.narx * item.miqdor;
      }

      const order = manager.create(Order, { user: { id: userId }, items, summa });
      return manager.save(order);                    // cascade — items ham (2.11)
    });
  }
}

Misol 4 — Query builder bilan relations (murakkab — 6.7, 6.13: 2.9)

ts
// Murakkab JOIN + agregat (query builder — 6.13: 2.9)
async statistika(userId: number) {
  return this.repo
    .createQueryBuilder("o")
    .leftJoinAndSelect("o.items", "item")            // JOIN (6.7)
    .leftJoinAndSelect("item.product", "product")
    .where("o.userId = :userId", { userId })         // parametrli (14)
    .andWhere("o.holat = :holat", { holat: "tugallandi" })
    .orderBy("o.createdAt", "DESC")
    .getMany();
}

// Faqat agregat (count/sum — 6.7: 2.3)
async jamiXarid(userId: number) {
  const r = await this.repo
    .createQueryBuilder("o")
    .select("SUM(o.summa)", "jami")
    .addSelect("COUNT(o.id)", "soni")
    .where("o.userId = :userId", { userId })
    .getRawOne();
  return { jami: Number(r.jami), soni: Number(r.soni) };
}

Misol 5 — N+1 muammosi va yechimi (2.12, 6.10)

ts
//  N+1 (lazy/tsikl — 6.10: 2.11)
async yomonUsul() {
  const orders = await this.repo.find();             // 1 so'rov
  for (const order of orders) {
    const user = await order.user;                   // lazy  N so'rov (har order!)
    console.log(user.email);
  }
  // 100 order  101 so'rov (sekin!)
}

//  relations (JOIN — bir so'rov — 2.10)
async yaxshiUsul() {
  const orders = await this.repo.find({
    relations: { user: true },                       // JOIN
  });
  for (const order of orders) {
    console.log(order.user.email);                   // allaqachon yuklangan (qo'shimcha so'rovsiz)
  }
  // 100 order  1 so'rov (tez!)
}

Misol 6 — Many-to-Many (toza, junctionsiz — 2.6)

ts
// Post  Tag (N:M — qo'shimcha maydon yo'q — toza @ManyToMany)
@Entity()
export class Post {
  @PrimaryGeneratedColumn() id: number;
  @Column() sarlavha: string;
  @ManyToMany(() => Tag, (tag) => tag.posts, { cascade: true })
  @JoinTable({ name: "post_tags" })                  // junction avtomatik (2.6)
  tags: Tag[];
}

@Entity()
export class Tag {
  @PrimaryGeneratedColumn() id: number;
  @Column({ unique: true }) nom: string;
  @ManyToMany(() => Post, (post) => post.tags) posts: Post[];
}

// Post + teglar bilan
async yarat(dto: CreatePostDto) {
  const tags = await this.tagRepo.findBy({ id: In(dto.tagIds) });   // mavjud teglar
  const post = this.postRepo.create({ sarlavha: dto.sarlavha, tags });
  return this.postRepo.save(post);                   // junction avtomatik to'ldiriladi
}

Misol 7 — Relations DTO bilan (8.5 — raw entity emas)

ts
// DTO 8.5-bob — nested relations uchun
export class CreateOrderItemDto {
  @IsNumber() productId: number;
  @IsNumber() @Min(1) miqdor: number;
}
export class CreateOrderDto {
  @IsArray()
  @ValidateNested({ each: true })                    // ichma-ich validatsiya (8.5)
  @Type(() => CreateOrderItemDto)                    // transform (class-transformer)
  items: CreateOrderItemDto[];
}

// Controller — DTO qabul qiladi (raw entity emas — 2.13)
@Post()
yarat(@Body() dto: CreateOrderDto, @Req() req) {
  return this.ordersService.yarat(req.user.id, dto);   // user — token'dan (8.9)
}

Misol 8 — Soft delete + relations (6.2: 2.11)

ts
@Entity()
export class Product {
  @PrimaryGeneratedColumn() id: number;
  @DeleteDateColumn() deletedAt: Date;               // soft delete (TypeORM)
  @OneToMany(() => OrderItem, (item) => item.product) orderItems: OrderItem[];
}

@Injectable()
export class ProductsService {
  constructor(@InjectRepository(Product) private repo: Repository<Product>) {}

  ochir(id: number) {
    return this.repo.softDelete(id);                 // deletedAt qo'yiladi (o'chirilmaydi)
  }
  hammasi() {
    return this.repo.find();                         // soft-deleted avtomatik chiqmaydi
  }
}
// Soft delete + relations — buyurtmadagi mahsulot "o'chsa" ham, tarix saqlanadi (6.3 snapshot)

Misol 9 — Self-relation (kategoriya daraxti — 2.17)

ts
// category.entity.ts — o'ziga 1:N (parent/children)
@Entity("categories")
export class Category {
  @PrimaryGeneratedColumn() id: number;
  @Column() nom: string;

  @ManyToOne(() => Category, (c) => c.children, { onDelete: "SET NULL", nullable: true })
  @JoinColumn({ name: "parent_id" })
  parent: Category | null;                           // root  NULL

  @OneToMany(() => Category, (c) => c.parent)
  children: Category[];
}

@Injectable()
export class CategoriesService {
  constructor(@InjectRepository(Category) private repo: Repository<Category>) {}

  // Daraxtni cheklangan chuqurlikda yuklash (N+1'siz — 2.12, 2.17)
  async daraxt() {
    return this.repo.find({
      where: { parent: IsNull() },                   // faqat root'lar
      relations: { children: { children: true } },   // 2 bosqich (aniq — cheksiz emas)
    });
  }

  // Bola qo'shish
  async qoshBola(parentId: number, nom: string) {
    const bola = this.repo.create({ nom, parent: { id: parentId } });
    return this.repo.save(bola);
  }
}

Misol 10 — Prisma relations (@relation, include/select — 2.14, 6.12)

prisma
// schema.prisma — relations (TypeORM entity'ga muqobil, type-safe)
model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  profile Profile?                                   // 1:1 (ixtiyoriy)
  orders  Order[]                                    // 1:N
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String?
  user   User   @relation(fields: [userId], references: [id])   // owning (FK shu yerda)
  userId Int    @unique                              // 1:1  @unique FK
}

model Order {
  id     Int         @id @default(autoincrement())
  user   User        @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId Int                                         // FK (N:1)
  items  OrderItem[]                                 // 1:N (junction)
}

model Product {
  id    Int      @id @default(autoincrement())
  nom   String
  tags  Tag[]    @relation("PostTags")               // N:M — Prisma junction AVTOMATIK
}

model Tag {
  id       Int       @id @default(autoincrement())
  nom      String    @unique
  products Product[] @relation("PostTags")
}
ts
// PrismaService bilan yuklash (include — TypeORM relations opsiyaga muqobil — 6.12: 2.12)
@Injectable()
export class OrdersService {
  constructor(private prisma: PrismaService) {}      // 8.3: 2.6

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

  // select — faqat kerakli maydonlar (over-fetch'siz — include o'rniga aniqroq)
  ruyxat() {
    return this.prisma.order.findMany({
      select: {
        id: true,
        user: { select: { email: true } },           // faqat email (butun user emas)
      },
    });
  }

  // Nested create (cascade'ga muqobil — Prisma nested write — 6.12: 2.13)
  yarat(userId: number, items: { productId: number; miqdor: number }[]) {
    return this.prisma.order.create({
      data: {
        user: { connect: { id: userId } },            // mavjud user'ga ulash
        items: { create: items.map((i) => ({ productId: i.productId, miqdor: i.miqdor })) },
      },
      include: { items: true },
    });
  }
}

Prisma relations@relation(fields, references) FK'ni belgilaydi (owning side — 2.4); yuklash include (butun relation) yoki select (aniq maydonlar — over-fetch'siz) bilan (TypeORM relations opsiyaga muqobil — 2.10). N:M — junction avtomatik (onDelete: Cascade schema'da). Nested write (create/connect) — TypeORM cascadega o'xshash (6.12: 2.13). Prisma eng type-safe (natija tipi include'ga qarab avtomatik chiqadi).


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

1) N+1 (lazy/tsikl)

ts
//  N+1 (2.12)
for (const o of orders) await o.user;

//  relations (JOIN)
repo.find({ relations: { user: true } });

2) Eager hamma joyda

ts
//  har relation eager (ortiqcha ma'lumot — 2.8)
@ManyToOne(() => User, { eager: true })

//  relations opsiya (so'rovga qarab — 2.10)
repo.find({ relations: { user: true } });

3) N:M + qo'shimcha maydon @ManyToMany bilan

text
 buyurtma mahsulotlariga miqdor — @ManyToMany (saqlay olmaydi — 2.7)
 junction entity (OrderItem — miqdor/narx)

4) Owning side noto'g'ri

text
 FK noto'g'ri tomonda (chalkash) — 2.4
 1:N  @ManyToOne (FK "ko'p" tomonda)

5) Raw entity controller'da

ts
//  entity to'g'ridan (validatsiyasiz, ortiqcha maydon — 8.5)
@Post() yarat(@Body() order: Order) {}

//  DTO
@Post() yarat(@Body() dto: CreateOrderDto) {}

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Relation undefined

Sababi: relations so'ralmagan, yoki eager yo'q 2.10-bob. Yechimi: relations: { x: true }; yoki eager.

Xato 2 — N+1 (sekin)

Sababi: lazy/tsikl 2.12-bob. Yechimi: relations/JOIN; DataLoader.

Xato 3 — Cannot read 'id' of undefined (relation save)

Sababi: relation obyekti to'liq emas (faqat id kerak). Yechimi: { user: { id } } yoki repository'dan to'liq obyekt.

Xato 4 — Circular relation (entity)

Sababi: ikki entity bir-birini import. Yechimi: () => Entity (lazy reference — dekoratorda).

Xato 5 — N:M qo'shimcha maydon yo'qoladi

Sababi: @ManyToMany faqat ID 2.7-bob. Yechimi: junction entity.

Xato 6 — Cascade kutilmagan yozish

Sababi: ortiqcha cascade 2.11-bob. Yechimi: cascade'ni faqat zarur relation'ga; ehtiyot.

Xato 7 — SET NULL migratsiyada xato beradi

Sababi: FK ustun nullable: false — NULL qo'yib bo'lmaydi 2.16-bob. Yechimi: ustunni nullable: true qiling (user: User | null).

Xato 8 — Parent o'chmaydi (RESTRICT / FK violation)

Sababi: bola qatorlar bor, onDelete: RESTRICT/NO ACTION 2.16-bob. Yechimi: avval bolalarni o'chiring/uzing, yoki biznesga qarab CASCADE/SET NULL tanlang.

Xato 9 — Self-relation daraxtida cheksiz/N+1 yuklash

Sababi: rekursiv relation cheklanmagan 2.17-bob. Yechimi: chuqurlikni aniq belgilang (children: { children: true }) yoki Tree Entity/rekursiv CTE.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • DB asoslari 6.1-bob: cardinality, relations, FK.
  • TypeORM 6.13-bob: relations dekoratorlari.
  • DB ulanish 8.3-bob: entity, repository.
  • JOIN/query 6.7-bob: query builder.
  • N+1/indeks 6.10-bob: performance.
  • ER modeling 6.15-bob: relations dizayni.
  • DTO 8.5-bob: nested validatsiya.
  • Prisma/Mongoose (6.12, 6.3): boshqa ORM relations.
  • Snapshot 6.3-bob: junction narx.

8. Eng yaxshi amaliyotlar (best practices)

  • ER modeling — relations'ni boshidan o'ylang 6.15-bob.
  • Owning side to'g'ri (FK joyi — 1:N @ManyToOne — 2.4).
  • relations opsiya (eager/lazy o'rniga — aniq, so'rovga qarab — 2.10).
  • N+1'dan saqlaning (relations/JOIN — 2.12, 6.10).
  • N:M + qo'shimcha junction entity (miqdor/narx — 2.7).
  • onDelete (DB) vs cascade (TypeORM) farqini biling 2.11-bob.
  • Snapshot (buyurtma narxi — junction — 6.3).
  • DTO orqali (raw entity emas — nested validatsiya — 8.5).
  • Tranzaksiya (nested create — zaxira — Misol 3).
  • Soft delete (relations bilan — tarix — Misol 8).

9. Amaliy loyiha: "E-commerce Ma'lumot Modeli (Relations)"

NestJS relations'ni mustahkamlash.

Maqsad

To'liq e-commerce ma'lumot modelini relations bilan qurish: 1:1, 1:N, N:M (junction), relations CRUD, N+1'siz.

Talablar (requirements)

  1. Entity'lar: User, Profile, Order, OrderItem, Product, Category — uch relation turi (Misol 1).
  2. 1:1: User Profile (cascade — 2.5).
  3. 1:N: User Order, Category Product (owning side — 2.3, 2.4).
  4. N:M + junction: Order Product (OrderItem — miqdor/narx snapshot — 2.7).
  5. N:M toza: Post Tag (junctionsiz — Misol 6, 2.6).
  6. Relations o'qish: nested relations opsiya (Misol 2, 2.10).
  7. Relations yaratish: cascade + tranzaksiya (Misol 3, 2.11).
  8. N+1 oldini olish: relations/JOIN (Misol 5, 2.12).
  9. DTO: nested validatsiya (Misol 7, 8.5).
  10. Query builder: statistika (Misol 4, 6.7).
  11. Self-relation: Category daraxti (parent/children — Misol 9, 2.17).
  12. onDelete: to'g'ri variant tanlash (CASCADE/SET NULL/RESTRICT — 2.16).

Maslahatlar (hint)

  • 1:N @ManyToOne (FK "ko'p" tomonda — 2.4).
  • N:M + maydon junction entity (2.7, 3-xato).
  • relations opsiya (eager emas — 2.10, 2-xato).
  • N+1 relations/JOIN (2.12, 1-xato).
  • Snapshot narx (junction — 6.3).
  • DTO + ValidateNested (8.5, Misol 7).

"Tayyor" mezonlari (acceptance criteria)

  • Uch relation turi (1:1, 1:N, N:M).
  • Junction entity (qo'shimcha maydon).
  • Owning side to'g'ri.
  • Relations o'qish (nested).
  • Relations yaratish (cascade + tranzaksiya).
  • N+1 yo'q (relations/JOIN).
  • DTO (nested validatsiya).
  • Query builder (statistika).
  • Self-relation (kategoriya daraxti — parent/children).
  • onDelete to'g'ri (biznes qoidasiga mos).
  • Snapshot (narx).
  • Soft delete (bonus).

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


10. Xulosa va keyingi bobga ko'prik

Bu bobda NestJS'da bog'lanishlarni chuqur o'rgandik:

  • Relations turlari (1:1, 1:N/N:1, N:M — cardinality — 2.1); dekoratorlar (@OneToOne/@OneToMany/@ManyToOne/@ManyToMany — 2.2).
  • 1:N (eng ko'p — 2.3); owning side (FK joyi — 2.4); 1:1 2.5-bob; N:M (@JoinTable — 2.6); junction entity (qo'shimcha maydon — 2.7).
  • Loading: eager 2.8-bob, lazy 2.9-bob, relations opsiya (eng yaxshi — 2.10); cascade 2.11-bob; N+1 (relations/JOIN — 2.12), DataLoader (batching — 2.18).
  • Relations CRUD (nested — 2.13); Prisma/Mongoose (kod bilan — 2.14, Misol 10); bidirectional/unidirectional 2.15-bob; onDelete variantlari (CASCADE/SET NULL/RESTRICT — 2.16); self-relation (daraxt/ierarxiya — 2.17, Misol 9); dizayn 2.19-bob.

Keyingi bob — 8.5-bob: DTO, validation pipes. Relations bilan ma'lumot modelini qurdik; endi kiruvchi ma'lumotni — DTO (Data Transfer Object) va validation pipes (class-validator) — chuqur o'rganamiz. "Foydalanuvchiga ishonmang" 5.9-bob — NestJS'da DTO + ValidationPipe + class-validator bilan. Bu — har endpoint'ning birinchi himoyasi.


Foydalanilgan rasmiy/ishonchli manbalar

  • docs.nestjs.com/techniques/database (relations); typeorm.io — Relations
  • DEV (graeyy) — Mastering TypeORM Relationships in NestJS; Medium — e-commerce entity example
  • Medium — N+1 query problem in NestJS (TypeORM, DataLoader)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.4-bob: Relations (1:1, 1:N, N:M) — Wisar