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)
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
@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 (
@OneToManybir tomonda,@ManyToOneboshqasida).@JoinColumn/@JoinTable— owning 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)
// 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)
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/@JoinTableqaysi tomonda. Inverse side (@OneToMany) — faqat navigatsiya (o'qish). Bu — qaysi tomondan saqlashni belgilaydi.
2.5. One-to-One (1:1)
// 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)
// 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@JoinTablefaqat ID bog'laydi.
2.7. Junction entity (qo'shimcha maydon bilan)
// 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
@ManyToManyfaqat 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)
// 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)
// 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 yuklanadiLazy loading — relation
Promise(kerak bo'lgandaawaitbilan yuklanadi). Foydali (kerakmas yuklamaydi), lekin N+1 xavfi (2.12 — tsiklda har element uchun alohida so'rov). Eager/lazy o'rniga ko'pincharelationsopsiya 2.10-bob afzal (aniq, so'rovga qarab).
2.10. relations opsiyasi (eng yaxshi — so'rovga qarab)
// 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)
}
relationsopsiya — 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)
// 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: true—save(order)order + items'ni birga saqlaydi (nested write — 6.12: 2.13 ruhida).cascade(TypeORM darajasi — save) vsonDelete: 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)
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'rovN+1 — NestJS+TypeORM'da eng ko'p performance xatosi (6.10: 2.11): lazy loading yoki tsikldagi alohida so'rov 1+N so'rov.
relationsopsiya (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)
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)
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)
// 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.userbor, lekinuser.ordersyo'q); ikkinchi (inverse) argument ((user) => user.orders) yozilmaydi. Bidirectional — ikkala tomondan navigatsiya mumkin (order.uservauser.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)
// 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 ustunNULLbo'ladi (bola qoladi, egasi yo'qoladi — ustunnullable: truebo'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 darajasidagicascade2.11-bob — boshqa narsa (save uchun). Qaysi birini tanlash — biznes qoidasiga bog'liq: audit/tarix kerak bo'lsaRESTRICTyoki soft delete (Misol 8), tozalash kerak bo'lsaCASCADE.
2.17. Self-relation (o'ziga bog'lanish — daraxt/ierarxiya)
// 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 elementdaNULL. Chuqur daraxtni yuklashda N+1/rekursiya xavfi 2.12-bob — bir necha bosqichnirelations: { 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 —parentham,childrenham kerak.
2.18. DataLoader — N+1'ni batching bilan yechish (GraphQL/murakkab)
// 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
relationsopsiya har doim mos kelmaydi). Ishlash tamoyili: bir event-loop "tick" ichida yig'ilgan barchaload(id)chaqiruvlarini bitta batch funksiyaga to'playdi va bittaIN (...)so'rov qiladi. Ikki qoida: (1) batch funksiya natijasi kirishidsbilan bir xil tartibda bo'lishi shart; (2)DataLoaderrequest-scoped (Scope.REQUEST) bo'lsin — aks holda keshi so'rovlar orasida "oqib ketadi" (eski ma'lumot). Oddiy REST'darelations/JOIN yetarli; DataLoader — murakkab graf yoki GraphQL uchun.
2.19. Relations dizayni (best practices — 6.15)
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
// 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)
// 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)
@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)
@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)
// 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)
// 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)
// 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)
// 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)
@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)
// 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)
// 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")
}// 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); yuklashinclude(butun relation) yokiselect(aniq maydonlar — over-fetch'siz) bilan (TypeORMrelationsopsiyaga muqobil — 2.10). N:M — junction avtomatik (onDelete: Cascadeschema'da). Nested write (create/connect) — TypeORMcascadega o'xshash (6.12: 2.13). Prisma eng type-safe (natija tipiinclude'ga qarab avtomatik chiqadi).
5. To'g'ri va noto'g'ri holatlar
1) N+1 (lazy/tsikl)
// N+1 (2.12)
for (const o of orders) await o.user;
// relations (JOIN)
repo.find({ relations: { user: true } });2) Eager hamma joyda
// 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
buyurtma mahsulotlariga miqdor — @ManyToMany (saqlay olmaydi — 2.7)
junction entity (OrderItem — miqdor/narx)4) Owning side noto'g'ri
FK noto'g'ri tomonda (chalkash) — 2.4
1:N @ManyToOne (FK "ko'p" tomonda)5) Raw entity controller'da
// 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)
- Entity'lar: User, Profile, Order, OrderItem, Product, Category — uch relation turi (Misol 1).
- 1:1: User Profile (cascade — 2.5).
- 1:N: User Order, Category Product (owning side — 2.3, 2.4).
- N:M + junction: Order Product (OrderItem — miqdor/narx snapshot — 2.7).
- N:M toza: Post Tag (junctionsiz — Misol 6, 2.6).
- Relations o'qish: nested relations opsiya (Misol 2, 2.10).
- Relations yaratish: cascade + tranzaksiya (Misol 3, 2.11).
- N+1 oldini olish: relations/JOIN (Misol 5, 2.12).
- DTO: nested validatsiya (Misol 7, 8.5).
- Query builder: statistika (Misol 4, 6.7).
- Self-relation: Category daraxti (parent/children — Misol 9, 2.17).
- 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!