Database Migrations (TypeORM)
Muammodan boshlaylik
Siz loyiha yozdingiz. synchronize: true bilan ishlayapsiz — entity o'zgarganda jadval avtomatik yangilanadi. Hammasi zo'r.
Keyin loyiha production ga chiqadi. Aytaylik, users jadvaliga yangi phone ustunini qo'shish kerak. Siz entity ga qo'shasiz:
@Column({ nullable: true })
phone: string;
synchronize: true production da yoqilgan bo'lsa — server qayta ishga tushganda TypeORM jadvalga phone ustunini qo'shadi. Bu yaxshi.
Lekin endi boshqa holat: siz name ustunini firstName va lastName ga ajratmoqchisiz.
// Eski
@Column()
name: string;
// Yangi
@Column()
firstName: string;
@Column()
lastName: string;
synchronize: true nima qiladi?
❌ Production da bo'ladigan narsa:
1. name ustuni o'chiriladi ← 50,000 foydalanuvchining ismi yo'qoladi
2. firstName ustuni qo'shiladi ← bo'sh
3. lastName ustuni qo'shiladi ← bo'sh
Ma'lumotlar yo'qoldi. Tiklab bo'lmaydi.
Aynan shu muammo uchun migration kerak.
Migration nima?
Migration — ma'lumotlar bazasiga qilinadigan o'zgarishlarni bosqichma-bosqich, nazorat ostida amalga oshirish usuli.
Hayotiy o'xshatma bilan tushuntiraman: ko'chmas mulk agenti binoni ta'mirlaydi. U har bir ishni qayd qiladi — "5-may: devorni bo'yadim, 6-may: deraza oldim, 7-may: parket yoqdim". Agar biror narsa noto'g'ri ketsa — "7-may dagi ishni qaytaraman" deb parketni ko'taradi.
Migration ham xuddi shunday — har bir o'zgarish qayd qilinadi. Kerak bo'lsa — orqaga qaytariladi.
synchronize: true Migration
─────────────────────────────────────────────────────
Avtomatik, nazorat yo'q │ Qo'lda, nazorat bor
Development uchun │ Production uchun
Ma'lumot yo'qolishi mumkin │ Ma'lumot saqlanadi
Orqaga qaytarib bo'lmaydi │ Orqaga qaytarish mumkin
Git da saqlanmaydi │ Git da saqlanadi (tarix)
Migration qanday ishlaydi?
migrations jadvali (PostgreSQL da)
┌─────┬────────────────────────────────────┬─────────────────────┐
│ id │ name │ timestamp │
├─────┼────────────────────────────────────┼─────────────────────┤
│ 1 │ 1700000000000-CreateUsersTable │ 2024-11-15 10:00:00 │
│ 2 │ 1700000001000-AddPhoneToUsers │ 2024-11-15 10:01:00 │
│ 3 │ 1700000002000-CreatePostsTable │ 2024-11-16 09:00:00 │
└─────┴────────────────────────────────────┴─────────────────────┘
TypeORM bu jadvalga qarab: "qaysi migrationlar allaqachon bajarilgan?"
deb biladi. Yangi migration fayllar bajarilmagan — ularni ishga tushiradi.
Har bir migration faylida ikkita metod bo'ladi:
up()— o'zgarishni qo'llash (oldinga)down()— o'zgarishni bekor qilish (orqaga)
O'rnatish va sozlash
1. typeorm-cli uchun alohida konfiguratsiya
TypeORM CLI — migration yaratish va ishga tushirish uchun buyruq qatori vositasi. U alohida konfiguratsiya fayl kerak qiladi:
// data-source.ts ← loyiha ildizida (src/ tashqarisida)
import { DataSource } from 'typeorm';
import * as dotenv from 'dotenv';
dotenv.config(); // .env ni o'qiydi
export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'mydb',
entities: ['src/**/*.entity{.ts,.js}'],
migrations: ['src/migrations/*{.ts,.js}'],
// migrations — migration fayllar qayerda saqlanadigini ko'rsatadi
});
Bu fayl app.module.ts dagi konfiguratsiyadan farq qiladi — faqat CLI uchun.
npm install dotenv
2. package.json ga skriptlar qo'shish
{
"scripts": {
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d data-source.ts",
"migration:create": "typeorm-ts-node-commonjs migration:create",
"migration:run": "typeorm-ts-node-commonjs migration:run -d data-source.ts",
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d data-source.ts",
"migration:show": "typeorm-ts-node-commonjs migration:show -d data-source.ts"
}
}
3. app.module.ts da synchronize o'chirish
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
// ...
synchronize: false, // ← endi migration ishlatamiz
migrations: ['dist/migrations/*{.ts,.js}'],
migrationsRun: true,
// migrationsRun: true — ilova ishga tushganda migrationlar avtomatik bajariladi
}),
inject: [ConfigService],
}),
Migration yaratish — ikkita usul
Usul 1: generate — avtomatik
Bu eng qulay usul. TypeORM entity lar va hozirgi baza holati orasidagi farqni o'zi topadi va migration yozadi.
npm run migration:generate -- src/migrations/CreateUsersTable
Entity: Baza: TypeORM nima qiladi?
──────────────────────────────────────────────────────────
users jadvali bor │ Baza bo'sh │ CREATE TABLE users yozadi
phone ustuni bor │ phone yo'q │ ALTER TABLE ADD COLUMN phone yozadi
name ustuni yo'q │ name bor │ ALTER TABLE DROP COLUMN name yozadi
Misol: User entity yaratdingiz — generate buyrug'i quyidagilarni yozadi:
// src/migrations/1700000000000-CreateUsersTable.ts
import { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateUsersTable1700000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// up() — bu migration ishga tushganda nima qilinadi
await queryRunner.query(`
CREATE TABLE "users" (
"id" SERIAL PRIMARY KEY,
"name" character varying(100) NOT NULL,
"email" character varying NOT NULL UNIQUE,
"password" character varying NOT NULL,
"role" "user_role_enum" NOT NULL DEFAULT 'user',
"isActive" boolean NOT NULL DEFAULT true,
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
"deletedAt" TIMESTAMP
)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// down() — bu migration orqaga qaytarilganda nima qilinadi
await queryRunner.query(`DROP TABLE "users"`);
}
}
Usul 2: create — qo'lda
Ba'zan TypeORM avtomatik topa olmaydigan o'zgarishlar bo'ladi — masalan, name ustunini firstName va lastName ga ajratish, ma'lumotlarni ko'chirish, index qo'shish.
npm run migration:create -- src/migrations/SplitNameColumn
Bu bo'sh migration fayl yaratadi — ichini o'zingiz to'ldirasiz:
// src/migrations/1700000003000-SplitNameColumn.ts
import { MigrationInterface, QueryRunner } from 'typeorm';
export class SplitNameColumn1700000003000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// 1. Yangi ustunlar qo'shamiz
await queryRunner.query(`
ALTER TABLE "users"
ADD COLUMN "firstName" character varying,
ADD COLUMN "lastName" character varying
`);
// 2. Mavjud ma'lumotlarni ko'chiramiz — name ni bo'sh joy bo'yicha ajratamiz
await queryRunner.query(`
UPDATE "users"
SET
"firstName" = SPLIT_PART(name, ' ', 1),
"lastName" = SPLIT_PART(name, ' ', 2)
`);
// 3. Eski ustunni o'chiramiz
await queryRunner.query(`
ALTER TABLE "users" DROP COLUMN "name"
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Orqaga qaytarish — firstName + lastName ni name ga birlashtiramiz
await queryRunner.query(`
ALTER TABLE "users" ADD COLUMN "name" character varying
`);
await queryRunner.query(`
UPDATE "users"
SET "name" = CONCAT("firstName", ' ', "lastName")
`);
await queryRunner.query(`
ALTER TABLE "users"
DROP COLUMN "firstName",
DROP COLUMN "lastName"
`);
}
}
Bu yondashuv bilan ma'lumot yo'qolmaydi — avval yangi ustunlar yaratiladi, ma'lumot ko'chiriladi, keyin eski ustun o'chiriladi.
Migration buyruqlari
migration:run — migrationlarni bajarish
npm run migration:run
Nima bo'ladi:
1. TypeORM migrations jadvalini tekshiradi
2. Bajarilmagan migration fayllarni topadi
3. Ularni timestamp tartibida ishga tushiradi
4. migrations jadvaliga yozadi — "bu bajarildi"
Natija:
query: SELECT * FROM "migrations" ORDER BY "id" DESC
query: START TRANSACTION
migration CreateUsersTable1700000000000 ishga tushdi
query: CREATE TABLE "users" (...)
query: INSERT INTO "migrations" (name) VALUES (...)
query: COMMIT
Migration muvaffaqiyatli bajarildi
migration:revert — orqaga qaytarish
npm run migration:revert
Faqat ENG SO'NGGI migration qaytariladi.
Yana qaytarmoqchi bo'lsangiz — yana shu buyruqni bering.
Nima bo'ladi:
1. migrations jadvalidagi oxirgi yozuvni topadi
2. Shu migration faylidagi down() ni ishga tushiradi
3. migrations jadvalidan o'chiradi
migration:show — holat
npm run migration:show
Natija:
[X] CreateUsersTable1700000000000 ← X = bajarilgan
[X] AddPhoneToUsers1700000001000
[X] CreatePostsTable1700000002000
[ ] SplitNameColumn1700000003000 ← bo'sh = bajarilmagan (kutmoqda)
Amaliy misol — to'liq jarayon
Keling, real loyihada migration qanday ishlatilishini ko'ramiz.
Boshlang'ich holat
// user.entity.ts — 1-versiya
@Entity('users')
export class User {
@PrimaryGeneratedColumn() id: number;
@Column() name: string;
@Column({ unique: true }) email: string;
}
# 1. Dastlabki migration yaratamiz
npm run migration:generate -- src/migrations/CreateUsersTable
# 2. Bajaramiz
npm run migration:run
users jadvali yaratildi:
┌────┬──────┬───────┐
│ id │ name │ email │
└────┴──────┴───────┘
O'zgarish: telefon raqam qo'shish
// user.entity.ts — 2-versiya
@Column({ nullable: true })
phone: string; // ← yangi maydon
# Farqni topib migration yaratadi
npm run migration:generate -- src/migrations/AddPhoneToUsers
TypeORM yaratgan fayl:
export class AddPhoneToUsers1700000001000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "users" ADD "phone" character varying
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "users" DROP COLUMN "phone"
`);
}
}
npm run migration:run
users jadvali yangilandi:
┌────┬──────┬───────┬───────┐
│ id │ name │ email │ phone │ ← phone qo'shildi, eski ma'lumotlar saqlanib qoldi
├────┼──────┼───────┼───────┤
│ 1 │ Ali │ali@.. │ NULL │
│ 2 │ Vali │vali@..│ NULL │
└────┴──────┴───────┴───────┘
Noto'g'ri migration — orqaga qaytarish
Aytaylik migration noto'g'ri yozildi. Orqaga qaytaramiz:
npm run migration:revert
AddPhoneToUsers migration bekor qilindi:
┌────┬──────┬───────┐
│ id │ name │ email │ ← phone ustuni o'chirildi
├────┼──────┼───────┤
│ 1 │ Ali │ali@.. │
│ 2 │ Vali │vali@..│
└────┴──────┴───────┘
Migration yozishda qoidalar
1. Har bir migration bitta vazifani bajarsin
// ❌ Yomon — bir migration da ko'p narsa
export class BigMigration implements MigrationInterface {
async up(queryRunner: QueryRunner) {
await queryRunner.query(`CREATE TABLE users (...)`);
await queryRunner.query(`CREATE TABLE posts (...)`);
await queryRunner.query(`ALTER TABLE users ADD phone ...`);
await queryRunner.query(`CREATE INDEX ...`);
}
}
// ✅ Yaxshi — alohida migrationlar
// 1700000000000-CreateUsersTable.ts
// 1700000001000-CreatePostsTable.ts
// 1700000002000-AddPhoneToUsers.ts
// 1700000003000-AddIndexToEmail.ts
2. down() ni har doim to'g'ri yozing
down() ni to'g'ri yozmasangiz — orqaga qaytarish imkoni yo'q bo'ladi. Revert qilgan vaqt muammo chiqadi.
// ❌ down() bo'sh qoldirilgan
async down(queryRunner: QueryRunner) {
// TODO: implement
}
// ✅ To'g'ri — up() ning teskarisi
async down(queryRunner: QueryRunner) {
await queryRunner.query(`DROP TABLE "users"`);
}
3. Migration fayllarini hech qachon o'zgartirmang
Migration bajarilgandan keyin uni o'zgartirmang. Agar xato topilsa — yangi migration yozing.
❌ Xato: Bajarilgan migration faylni tahrirlash
→ Baza bilan fayl mos kelmay qoladi
→ Boshqa dasturchilarda xato chiqadi
✅ To'g'ri: Yangi migration fayl yarating
4. Git ga commit qiling
git add src/migrations/
git commit -m "feat: add phone column to users table"
Migration fayllar git tarixida saqlanishi shart — boshqa dasturchilarga ham, production serverga ham kerak bo'ladi.
Production da migration
Production da migrationlar ikki xil ishga tushiriladi:
1-usul: migrationsRun: true — avtomatik
// app.module.ts
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
// ...
migrationsRun: true,
// Ilova ishga tushganda bajarilmagan migrationlar avtomatik ishga tushadi
migrations: ['dist/migrations/*{.js}'],
}),
})
CI/CD pipeline da ilova deploy qilinganda migrationlar avtomatik bajariladi.
2-usul: Deploy skriptida qo'lda
# deploy.sh
echo "Migrationlar ishga tushirilmoqda..."
npm run migration:run
echo "Ilova ishga tushirilmoqda..."
npm run start:prod
💡 Qaysi usul yaxshiroq? Ko'pchilik
migrationsRun: trueni tavsiya qiladi — migration va deploy birgalikda bo'ladi, alohida qadam kerak emas.
generate vs create — qachon qaysi?
migration:generate migration:create
──────────────────────────────────────────────────────────
Entity o'zgarganda │ Ma'lumotlarni ko'chirish kerak bo'lsa
Ustun qo'shish / o'chirish │ Eski ustunni yangi nomga o'tkazish
Jadval yaratish / o'chirish │ Bir jadvaldan boshqasiga ma'lumot ko'chirish
TypeORM o'zi SQL yozadi │ Index, trigger, view qo'shish
│ Maxsus PostgreSQL SQL kerak bo'lsa
Vazifalar
1-vazifa — Tushunish:
synchronize: true va migration ning farqi nima? Quyidagi holatda qaysi birini ishlatish kerak?
- Uy da o'z kompyuteringizda yangi feature sinab ko'ryapsiz
- Haqiqiy foydalanuvchilar bor production server
2-vazifa — Amaliy:
Post entity ga viewCount (integer, default: 0) ustunini qo'shing. Migration yozib, up() da ustun qo'shilsin, down() da o'chirilsin.
3-vazifa — O'ylash:
Aytaylik posts jadvalida 100,000 ta yozuv bor. title ustunini varchar(100) dan varchar(500) ga o'zgartirmoqchisiz. Bu migration production da qancha vaqt olishi mumkin? Xavfli tomonlari bormi?
Xulosa
Migration nima: Ma'lumotlar bazasiga qilinadigan o'zgarishlarni nazorat ostida, qaytarish imkoni bilan amalga oshirish.
Nima uchun kerak: synchronize: true production da ma'lumot yo'qotishi mumkin. Migration esa ma'lumotni saqlab, o'zgarishni xavfsiz qo'llaydi.
Ikki usul:
generate— entity o'zgarganda, TypeORM SQL ni o'zi yozadicreate— ma'lumot ko'chirish yoki maxsus SQL kerak bo'lganda, qo'lda yoziladi
Asosiy buyruqlar:
migration:run— bajarilmagan migrationlarni ishga tushiradimigration:revert— oxirgi migrationni orqaga qaytaradimigration:show— qaysilar bajarilgan, qaysilar kutmoqda
Qoidalar:
- Har bir migration — bitta vazifa
down()ni har doim to'g'ri yozing- Bajarilgan migration faylni o'zgartirmang
- Git ga commit qiling
Keyingi dars
19-dars: TypeORM — Prisma bilan PostgreSQL