Database Relations (TypeORM)
Muammodan boshlaylik
Foydalanuvchi ro'yxatdan o'tdi. Uning profil ma'lumotlari bor — bio, avatar, telefon raqam. U postlar yozadi. Har bir postda teglar (tag) bor.
Bularni qanday saqlash kerak? Hammani bitta jadvalga joylashtirish mumkinmi?
❌ Yomon usul — bitta katta jadval:
| id | ism | bio | post1_sarlavha | post1_matn | post2_sarlavha | tag1 | tag2 |
|----|------|------------|----------------|------------|----------------|-------|--------|
| 1 | Ali | Dasturchi | Salom dunyo | ... | TypeORM haqida | nestjs| typeorm|
| 2 | Vali | Dizayner | NULL | NULL | NULL | NULL | NULL |
Bu yondashuv bilan muammolar:
- Ali 10 ta post yozsa — 10 ta ustun kerakmi?
- Bitta postda 20 ta tag bo'lsa — 20 ta ustun?
- Vali hech post yozmagan — jadvalda bo'sh (NULL) kataklar to'lib ketadi
Yechim — ma'lumotlarni alohida jadvallarga ajratish va ular orasida bog'liqlik (relation) o'rnatish.
Bog'liqlik nima?
Bog'liqlik — ikki jadval orasidagi aloqa. Bir jadvaldagi ma'lumot boshqa jadvaldagi ma'lumotga "ishora" qiladi.
✅ To'g'ri usul — alohida jadvallar:
users jadvali posts jadvali
┌────┬──────┐ ┌────┬─────────────────┬─────────┐
│ id │ ism │ │ id │ sarlavha │ user_id │
├────┼──────┤ ├────┼─────────────────┼─────────┤
│ 1 │ Ali │◄───────────│ 1 │ Salom dunyo │ 1 │
│ 2 │ Vali │ │ 2 │ TypeORM haqida │ 1 │
└────┴──────┘ │ 3 │ CSS tricks │ 2 │
└────┴─────────────────┴─────────┘
│
user_id = 1 degani:
"bu post Ali nikiga tegishli"
posts jadvalidagi user_id ustuni — foreign key (tashqi kalit). U users jadvalidagi id ga ishora qiladi. Shu orqali "qaysi post kimniki" ma'lum bo'ladi.
Bog'liqlik turlari
Uchta asosiy tur bor:
One-to-One │ One-to-Many │ Many-to-Many
─────────────────────────────────────────────
User ──── Profile │ User ──< Posts │ Posts >──< Tags
│ │
1 foydalanuvchi │ 1 foydalanuvchi │ 1 post ko'p
= 1 profil │ = ko'p postlar │ tegga ega bo'lishi
│ │ mumkin va 1 teg
1 profil │ 1 post faqat │ ko'p postda
= 1 foydalanuvchi │ 1 foydalanuvchiga│ ishlatilishi mumkin
Har birini alohida ko'ramiz.
1. One-to-One — birga bir
Qoidasi: A jadvalning har bir yozuvi B jadvalning faqat bitta yozuvi bilan bog'liq. Va aksincha.
Misol: Har bir foydalanuvchining bitta profili bor. Har bir profil faqat bitta foydalanuvchiga tegishli.
users jadvali profiles jadvali
┌────┬───────┬────────┐ ┌────┬──────────┬───────┬─────────┐
│ id │ name │ email │ │ id │ bio │ phone │ user_id │
├────┼───────┼────────┤ ├────┼──────────┼───────┼─────────┤
│ 1 │ Ali │ ali@.. │◄─│ 1 │Dasturchi │998901 │ 1 │
│ 2 │ Vali │vali@.. │◄─│ 2 │Dizayner │998902 │ 2 │
└────┴───────┴────────┘ └────┴──────────┴───────┴─────────┘
profiles.user_id → users.id
(foreign key profiles jadvalida yotadi)
Entity lar:
// users/entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm';
import { Profile } from '../../profiles/entities/profile.entity';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@OneToOne(() => Profile, (profile) => profile.user, {
cascade: true, // user saqlanganda profile ham saqlansin
eager: false, // user olinganda profile avtomatik kelmaydi
})
profile: Profile;
}
// profiles/entities/profile.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm';
import { User } from '../../users/entities/user.entity';
@Entity('profiles')
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column({ nullable: true })
bio: string;
@Column({ nullable: true })
phone: string;
@Column({ nullable: true })
avatar: string;
@OneToOne(() => User, (user) => user.profile)
@JoinColumn() // ← shu dekorator foreign key qaysi jadvalda bo'lishini belgilaydi
user: User;
}
@JoinColumn() nima qiladi?
@JoinColumn() — foreign key (user_id ustuni) shu jadvalda bo'lishini aytadi. Uni faqat bir tomonga — bog'liqlikni "saqlovchi" tomonga yoziladi.
@JoinColumn() profiles da yozildi
↓
profiles jadvalida user_id ustuni paydo bo'ladi
users jadvalida hech qanday qo'shimcha ustun bo'lmaydi
Ishlatish — service da:
// Profile bilan birga user yaratish
async create(createUserDto: CreateUserDto) {
const user = this.userRepository.create({
name: createUserDto.name,
email: createUserDto.email,
profile: {
bio: createUserDto.bio,
phone: createUserDto.phone,
},
// cascade: true bo'lgani uchun profile ham avtomatik saqlanadi
});
return await this.userRepository.save(user);
}
// User ni profili bilan birga olish
async findOneWithProfile(id: number) {
return await this.userRepository.findOne({
where: { id },
relations: { profile: true },
// relations — qo'shimcha JOIN qilib profil ham olinsin
});
}
Natija:
{
"id": 1,
"name": "Ali",
"email": "ali@example.com",
"profile": {
"id": 1,
"bio": "Dasturchi",
"phone": "998901234567",
"avatar": null
}
}
2. One-to-Many / Many-to-One — birga ko'p
Qoidasi: A jadvalning bitta yozuvi B jadvalning ko'p yozuvlari bilan bog'liq. Lekin B jadvalning har bir yozuvi faqat A ning bitta yozuviga tegishli.
Misol: Bir foydalanuvchi ko'p post yozishi mumkin. Lekin har bir post faqat bitta foydalanuvchiga tegishli.
users jadvali posts jadvali
┌────┬───────┐ ┌────┬──────────────────┬─────────┐
│ id │ name │ │ id │ title │ user_id │
├────┼───────┤ ├────┼──────────────────┼─────────┤
│ │ │◄────────│ 1 │ Salom dunyo │ 1 │
│ 1 │ Ali │◄────────│ 2 │ NestJS haqida │ 1 │
│ │ │◄────────│ 3 │ TypeORM — CRUD │ 1 │
├────┼───────┤ ├────┼──────────────────┼─────────┤
│ 2 │ Vali │◄────────│ 4 │ CSS qo'llanma │ 2 │
└────┴───────┘ └────┴──────────────────┴─────────┘
Ali = 3 ta post Vali = 1 ta post
foreign key (user_id) DOIM "ko'p" tomonda yotadi — ya'ni posts da
Bu ikki tomoni bor:
User→Posttomoni: One-to-Many (bir user — ko'p post)Post→Usertomoni: Many-to-One (ko'p post — bir user)
Entity lar:
// users/entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Post } from '../../posts/entities/post.entity';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, (post) => post.user)
// "Men bir — Post lar ko'p. Post lardagi 'user' maydoni menga qaraydi."
posts: Post[];
}
// posts/entities/post.entity.ts
import {
Entity, PrimaryGeneratedColumn, Column,
ManyToOne, JoinColumn, CreateDateColumn
} from 'typeorm';
import { User } from '../../users/entities/user.entity';
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column('text')
content: string;
@CreateDateColumn()
createdAt: Date;
@ManyToOne(() => User, (user) => user.posts, {
onDelete: 'CASCADE',
// User o'chirilsa — uning postlari ham o'chsin
// onDelete: 'SET NULL' — user_id ni NULL ga qo'ysin (nullable: true kerak)
// onDelete: 'RESTRICT' — user ni o'chirsa xato bering (standart)
})
// @JoinColumn() shart emas — Many tomonda foreign key avtomatik paydo bo'ladi
user: User;
}
@JoinColumn() kerakmi? One-to-Many / Many-to-One da @JoinColumn() yozmaslik ham bo'ladi — TypeORM ManyToOne tomoniga avtomatik user_id ustunini qo'shadi. Lekin yozsa ham ishlaydi.
Ishlatish — service da:
// Post yaratish
async createPost(userId: number, createPostDto: CreatePostDto) {
const user = await this.userRepository.findOne({ where: { id: userId } });
if (!user) throw new NotFoundException('Foydalanuvchi topilmadi');
const post = this.postRepository.create({
...createPostDto,
user, // ← bog'liqlik shu yerda o'rnatiladi
});
return await this.postRepository.save(post);
// SQL: INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)
}
// Foydalanuvchining barcha postlarini olish
async findUserPosts(userId: number) {
return await this.postRepository.find({
where: { user: { id: userId } },
order: { createdAt: 'DESC' },
});
}
// User ni postlari bilan birga olish
async findUserWithPosts(id: number) {
return await this.userRepository.findOne({
where: { id },
relations: { posts: true },
});
}
Natija:
{
"id": 1,
"name": "Ali",
"posts": [
{ "id": 1, "title": "Salom dunyo", "createdAt": "2026-01-01" },
{ "id": 2, "title": "NestJS haqida", "createdAt": "2026-01-02" },
{ "id": 3, "title": "TypeORM — CRUD", "createdAt": "2026-01-03" }
]
}
3. Many-to-Many — ko'pga ko'p
Qoidasi: A jadvalning ko'p yozuvlari B jadvalning ko'p yozuvlari bilan bog'liq bo'lishi mumkin.
Misol: Bitta postda bir nechta teg bo'lishi mumkin. Va bitta teg bir nechta postda ishlatilishi mumkin.
posts jadvali (oraliq jadval) tags jadvali
┌────┬──────────┐ ┌─────────┬────────┐ ┌────┬─────────┐
│ id │ title │ │ post_id │ tag_id │ │ id │ name │
├────┼──────────┤ ├─────────┼────────┤ ├────┼─────────┤
│ 1 │ NestJS │──►│ 1 │ 1 │◄──│ 1 │ nestjs │
│ │ │──►│ 1 │ 2 │◄──│ 2 │typeorm │
│ │ │ │ 1 │ 3 │◄──│ 3 │postgres │
│ 2 │ CSS tips │──►│ 2 │ 4 │◄──│ 4 │ css │
│ │ │──►│ 2 │ 1 │ └────┴─────────┘
└────┴──────────┘ └─────────┴────────┘
Post 1 (NestJS): nestjs, typeorm, postgres teglari bor
Post 2 (CSS tips): css, nestjs teglari bor
"nestjs" tegi 2 ta postda ishlatilgan
Ko'rinib turibdiki, Many-to-Many bog'liqlikda oraliq jadval paydo bo'ladi. Bu jadval ikki tomonning ID larini saqlaydi. TypeORM bu jadvalni avtomatik yaratadi — siz yaratishingiz shart emas.
Entity lar:
// posts/entities/post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { Tag } from '../../tags/entities/tag.entity';
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column('text')
content: string;
@ManyToMany(() => Tag, (tag) => tag.posts, { cascade: true })
@JoinTable()
// @JoinTable() — oraliq jadvalni qaysi entity boshqarishini belgilaydi
// Faqat bir tomonga yoziladi — odatda "asosiy" tomonga (bu yerda Post)
tags: Tag[];
}
// tags/entities/tag.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm';
import { Post } from '../../posts/entities/post.entity';
@Entity('tags')
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
name: string;
@ManyToMany(() => Post, (post) => post.tags)
// @JoinTable() bu tomonda yo'q — Post tomonida bor
posts: Post[];
}
Ishlatish — service da:
// Post teglar bilan yaratish
async createPost(createPostDto: CreatePostDto) {
const { tagIds, ...postData } = createPostDto;
// Teglarni bazadan topamiz
const tags = await this.tagRepository.findBy({
id: In(tagIds), // In([1, 2, 3]) — id si 1, 2 yoki 3 ga teng
});
const post = this.postRepository.create({
...postData,
tags, // ← teglar ro'yxati shu yerda biriktiriladi
});
return await this.postRepository.save(post);
// TypeORM o'zi oraliq jadvalga yozadi:
// INSERT INTO post_tags_tag (post_id, tag_id) VALUES (1,1), (1,2), (1,3)
}
// Post ni teglari bilan olish
async findPostWithTags(id: number) {
return await this.postRepository.findOne({
where: { id },
relations: { tags: true },
});
}
// Postga teg qo'shish
async addTag(postId: number, tagId: number) {
const post = await this.postRepository.findOne({
where: { id: postId },
relations: { tags: true },
});
const tag = await this.tagRepository.findOneBy({ id: tagId });
post.tags = [...post.tags, tag];
return await this.postRepository.save(post);
}
// Postdan teg olib tashlash
async removeTag(postId: number, tagId: number) {
const post = await this.postRepository.findOne({
where: { id: postId },
relations: { tags: true },
});
post.tags = post.tags.filter(tag => tag.id !== tagId);
return await this.postRepository.save(post);
}
relations — qanday yuklanadi?
TypeORM da bog'liq ma'lumotlar avtomatik kelmaydi. Siz so'ramasangiz — faqat asosiy jadval ma'lumotlari keladi.
// Faqat user — postlarsiz
const user = await userRepository.findOne({ where: { id: 1 } });
// { id: 1, name: "Ali", email: "..." }
// posts: undefined ← kelmadi!
// User postlari bilan
const user = await userRepository.findOne({
where: { id: 1 },
relations: { posts: true },
// SQL: SELECT * FROM users LEFT JOIN posts ON posts.user_id = users.id
});
// { id: 1, name: "Ali", posts: [...] }
// Ichma-ich relations — postlarning teglari ham kerak bo'lsa
const user = await userRepository.findOne({
where: { id: 1 },
relations: {
posts: {
tags: true, // posts ni olib, ichidagi tags ni ham ol
},
},
});
Nima uchun avtomatik kelmaydi? Tasavvur qiling: foydalanuvchini olishda u yozgan barcha postlar, har bir postning teglari, kommentariyalar... hammasi avtomatik yuklanib ketsa — bitta so'rovda yuzlab jadvaldan ma'lumot tortib olinadi. Bu ishlashni keskin sekinlashtiradi. Shuning uchun TypeORM "kerak bo'lsa o'zing so'ra" prinsipi bilan ishlaydi.
eager — har doim birga yuklansin
Agar relation har doim birga kelishi kerak bo'lsa — eager: true yoziladi:
@OneToMany(() => Post, (post) => post.user, {
eager: true, // user olinganda postlari har doim birga keladi
})
posts: Post[];
// eager: true bo'lganda relations yozmasdan ham postlar keladi
const user = await userRepository.findOne({ where: { id: 1 } });
// { id: 1, name: "Ali", posts: [...] } ← postlar avtomatik keldi
⚠️
eager: trueehtiyotkorlik bilan ishlatilsin. Foydalanuvchilar ro'yxatini olsangiz (findAll) — har bir foydalanuvchining barcha postlari ham yuklanadi. 100 ta foydalanuvchi × 50 ta post = 5000 ta yozuv bir so'rovda. Bu jiddiy muammo bo'lishi mumkin.
cascade — birga saqla, birga o'chir
cascade — bog'liq yozuvlar bilan nima qilish kerakligini belgilaydi.
@OneToMany(() => Post, (post) => post.user, {
cascade: true,
// cascade: true degani — ['insert', 'update', 'remove', 'soft-remove', 'recover'] hammasi
// cascade: ['insert', 'update'] — faqat qo'shish va yangilash
})
posts: Post[];
Amalda nima qiladi:
// cascade: true — user bilan birga post ham saqlanadi
const user = this.userRepository.create({
name: 'Ali',
email: 'ali@example.com',
posts: [
{ title: 'Birinchi post', content: '...' },
{ title: 'Ikkinchi post', content: '...' },
],
});
await this.userRepository.save(user);
// 1. users jadvaliga yoziladi
// 2. posts jadvaliga ham yoziladi — postRepository.save() shart emas
onDelete — bog'liq yozuv o'chirilganda nima bo'lishi:
@ManyToOne(() => User, (user) => user.posts, {
onDelete: 'CASCADE', // user o'chirilsa post ham o'chsin
// onDelete: 'SET NULL' — user_id = NULL bo'lsin (nullable: true kerak)
// onDelete: 'RESTRICT' — user o'chirib bo'lmaydi, avval postlarni o'chir
})
user: User;
Uchala bog'liqlikni taqqoslash
┌──────────────┬──────────────────────┬────────────────────────┬────────────────────────┐
│ │ One-to-One │ One-to-Many │ Many-to-Many │
├──────────────┼──────────────────────┼────────────────────────┼────────────────────────┤
│ Misol │ User ↔ Profile │ User → Posts │ Posts ↔ Tags │
├──────────────┼──────────────────────┼────────────────────────┼────────────────────────┤
│ Foreign key │ Bir tomonda │ "Ko'p" tomonda │ Oraliq jadvalda │
│ qayerda? │ (@JoinColumn da) │ (posts.user_id) │ (post_tags_tag) │
├──────────────┼──────────────────────┼────────────────────────┼────────────────────────┤
│ Dekoratorlar │ @OneToOne │ @OneToMany │ @ManyToMany │
│ │ @JoinColumn │ @ManyToOne │ @JoinTable │
├──────────────┼──────────────────────┼────────────────────────┼────────────────────────┤
│ Oraliq jadval│ Yo'q │ Yo'q │ Ha — avtomatik │
└──────────────┴──────────────────────┴────────────────────────┴────────────────────────┘
Loyiha tuzilishi
src/
├── users/
│ ├── entities/user.entity.ts ← @OneToOne(Profile), @OneToMany(Post)
│ └── users.module.ts ← forFeature([User])
├── profiles/
│ ├── entities/profile.entity.ts ← @OneToOne(User), @JoinColumn()
│ └── profiles.module.ts ← forFeature([Profile])
├── posts/
│ ├── entities/post.entity.ts ← @ManyToOne(User), @ManyToMany(Tag), @JoinTable()
│ └── posts.module.ts ← forFeature([Post])
└── tags/
├── entities/tag.entity.ts ← @ManyToMany(Post)
└── tags.module.ts ← forFeature([Tag])
Vazifalar
1-vazifa — Tushunish: Quyidagi holatda qaysi bog'liqlik turi ishlatiladi va foreign key qaysi jadvalda bo'ladi?
- Shahar va mamlakat (bir mamlakatda ko'p shahar)
- Talaba va kurs (bir talaba ko'p kursga yozilishi mumkin, bir kursda ko'p talaba)
- Kompaniya va CEO (bir kompaniyaning bitta bosh direktori)
2-vazifa — Amaliy:
Category entity yarating. Post bilan One-to-Many bog'liqlik o'rnating: bir kategoriyada ko'p post bo'lishi mumkin, lekin har bir post faqat bitta kategoriyaga tegishli.
3-vazifa — O'ylash:
eager: true ni har doim yoqib qo'ysak nima muammo bo'ladi? Qachon eager: true, qachon relations: { ... } ishlatish to'g'riroq?
Xulosa
One-to-One — bitta yozuv bitta yozuv bilan. @JoinColumn() foreign key qayerda bo'lishini belgilaydi.
One-to-Many / Many-to-One — bitta yozuv ko'p yozuv bilan. Foreign key doim "ko'p" tomonda (posts.user_id). @OneToMany va @ManyToOne juft ishlatiladi.
Many-to-Many — ko'p yozuv ko'p yozuv bilan. TypeORM oraliq jadvalni avtomatik yaratadi. @JoinTable() faqat bir tomonga yoziladi.
relations — bog'liq ma'lumotlar avtomatik kelmaydi, relations: { posts: true } bilan so'raladi.
cascade — bog'liq yozuvlarni birga saqlash/o'chirish uchun.
onDelete — bog'liq yozuv o'chirilganda nima bo'lishi: CASCADE, SET NULL, RESTRICT.
Keyingi dars
18-dars: TypeORM — Migration