Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Entity: jadval — klass ko'rinishida
- 2.1 Birinchi entity
- 2.2 Node type stripping dekoratorni bajarmaydi
- 3. DataSource va synchronize
- 3.1 Ulanish
- 3.2 synchronize: true — bo'sh bazada
- 4. Repository: CRUD
- 4.1 save va find
- 4.2 find va where
- 4.3 Operatorlar, update va delete
- 5. Relation'lar: relations, eager, lazy
- 5.1 relations — so'raganda yuklash
- 5.2 eager: true — doim yuklash
- 5.3 lazy — Promise orqali
- 6. QueryBuilder
- 7. Migratsiyalar va synchronize xavfi
- 7.1 synchronize mavjud bazada
- 7.2 migration:generate
- 7.3 run, show, revert
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson)
- 2-mashq (o'rta)
- 3-mashq (qiyin)
- 10. Loyiha qadami
- 11. Real ishda
- Xulosa
- Manbalar
TypeORM: entity, DataSource, Repository, QueryBuilder va migratsiyalar
Qisqacha: TypeORM — jadvalni dekoratorli klass (entity) sifatida yozadigan ORM:
@Entity,@Column,@ManyToOne. Ulanish va sozlama —DataSource, so'rovlar —Repository(find,save,delete) yoki SQL'ga yaqinQueryBuilder. NestJS dunyosida hali ham keng tarqalgan. Eng katta tuzog'i —synchronize: true: biz o'lchadik, ustun turini o'zgartirsangiz TypeORMDROP COLUMN+ADDqiladi va ma'lumot yo'qoladi. Prodakshnda sxema faqat migratsiya bilan o'zgaradi.
Bu darsda
- TypeORM entity'ni dekoratorlar bilan yozasiz va u qanday jadvalga aylanishini ko'rasiz.
DataSource,RepositoryvaQueryBuilderbilan o'qish va yozishni bajarasiz.- Relation'lar (
@ManyToOne,@OneToMany),relationsvaeagerfarqini tushunasiz. synchronizenega prodakshnda taqiqlanishini haqiqiy natijada ko'rasiz.migration:generate,migration:run,migration:revertbuyruqlarini ishlatasiz.
Oldin bilishingiz kerak: ORM, query builder va raw SQL, Legacy dekoratorlar va reflect-metadata, Kysely va Knex query builder'lari, Foreign key va referential actions.
1. Nega bu kerak?
Sardor ishga kirdi. Birinchi kuni unga 2021-yilda yozilgan NestJS loyihasini berishdi. Fayllarni ochadi — user.entity.ts, order.entity.ts, har birida @Entity(), @Column(), @ManyToOne(). Prisma'dagi schema.prisma ham, Drizzle'dagi pgTable ham yo'q. Bu — TypeORM.
Prisma kursning asosiy tanlovi bo'lsa ham, TypeORM'ni tanish shart. Sabablari oddiy:
- NestJS hujjatlari uzoq yillar TypeORM'ni birinchi o'ringa qo'ygan. Minglab loyihalar shu bilan yozilgan.
- Java (Hibernate) yoki C# (Entity Framework) dan kelgan dasturchilar aynan shu uslubni yaxshi ko'radi.
- Eski loyihani qo'llab-quvvatlash — kichik jamoalarda eng ko'p uchraydigan ish.
O'xshatish: Prisma — zamonaviy avtomat mashina, TypeORM — mexanik uzatmali mashina. Ikkalasi ham yuradi. Lekin mexanikada qachon debrizka bosishni bilmasangiz, mashina o'chib qoladi. TypeORM'ning "debrizkasi" — synchronize va relation'lar. Bu darsda aynan shularni o'lchaymiz.
Maslahat: Bu darsdagi hamma natijani biz o'zimiz ishga tushirdik: TypeORM 1.1.2,
pg8.23.1, TypeScript 6.0.3, Node 24.21.0, PostgreSQL 17.6, Windows 11 (2026-10-25). Kanonning tezlik o'lchovlari esa ORM, query builder va raw SQL darsidagikattabazasida olingan.
2. Entity: jadval — klass ko'rinishida
2.1 Birinchi entity
Entity (mohiyat) — bazadagi bitta jadvalni ifodalaydigan klass. Har xususiyat (property) — ustun. Har obyekt — bitta qator.
TypeORM entity'ni dekoratorlar bilan tavsiflaydi. Dekoratorlarni 15-qismda o'rgangan edik (Legacy dekoratorlar). TypeORM aynan legacy dekoratorlardan foydalanadi: tsconfig.json da ikki sozlama shart.
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false
}
}emitDecoratorMetadata — TypeScript har xususiyatning turini (string, number) metama'lumotga yozadi. TypeORM shu orqali @Column() ning turini biladi. strictPropertyInitialization: false — aks holda email: string; qatoriga "boshlang'ich qiymat yo'q" xatosi chiqadi.
Bizning vazifalar domenidagi uchta entity (src/entities.ts):
import {
Entity, PrimaryGeneratedColumn, Column, ManyToOne,
OneToMany, CreateDateColumn, Index, JoinColumn,
} from "typeorm";
@Entity({ name: "foydalanuvchilar" })
export class Foydalanuvchi {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column({ name: "toliq_ism", length: 100 })
toliqIsm: string;
@OneToMany(() => Vazifa, (v) => v.egasi)
vazifalar: Vazifa[];
}
@Entity({ name: "vazifalar" })
export class Vazifa {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: "text" })
matn: string;
@Column({ default: false })
bajarildi: boolean;
@CreateDateColumn({
name: "yaratilgan_vaqt", type: "timestamptz" })
yaratilganVaqt: Date;
@Index()
@ManyToOne(() => Foydalanuvchi, (f) => f.vazifalar, {
nullable: false, onDelete: "CASCADE",
})
@JoinColumn({ name: "egasi_id" })
egasi: Foydalanuvchi;
@OneToMany(() => Izoh, (i) => i.vazifa)
izohlar: Izoh[];
}Izoh entity'si xuddi shunday: id, matn va @ManyToOne(() => Vazifa) + @JoinColumn({ name: "vazifa_id" }).
Har dekorator nima qiladi:
| Dekorator | Ma'nosi |
|---|---|
@Entity({ name }) |
Klass — jadval; name — jadval nomi |
@PrimaryGeneratedColumn() |
Avtomatik o'suvchi birlamchi kalit |
@Column(opts) |
Oddiy ustun: type, length, default, unique, nullable, name |
@CreateDateColumn() |
Yaratilgan vaqt — qiymatni baza qo'yadi |
@ManyToOne + @JoinColumn |
Tashqi kalit shu jadvalda (egasi_id) |
@OneToMany |
Teskari tomon — ustun yaratmaydi |
@Index() |
Shu ustunga indeks |
name: "toliq_ism" ga e'tibor bering. JS'da toliqIsm (camelCase), bazada toliq_ism (snake_case). Prisma'dagi @map bilan bir xil g'oya.
2.2 Node type stripping dekoratorni bajarmaydi
24-qismda Node 24 .ts faylni o'zi ishga tushirishini ko'rgan edik (type stripping). Lekin u faqat turlarni o'chiradi. Dekorator — tur emas, ishlaydigan kod. Kanon tajribasi buni aniq qayd etgan: dekoratorli entity Node type stripping'da ishlamadi, shuning uchun kanon o'lchovi dekoratorsiz EntitySchema shaklini ishlatdi.
Shuning uchun TypeORM loyihasi odatda avval kompilyatsiya qilinadi (tsc → dist/), keyin node dist/...js. Biz ham shunday qildik.
Tekshirib ko'ring:
@OneToManyqo'yilganvazifalarxususiyati uchun bazada ustun bormi?
Javob
Yo'q. @OneToMany — teskari tomon, faqat JS'da "bu foydalanuvchining vazifalari" ro'yxatini yuklash uchun. Tashqi kalit ustuni (egasi_id) har doim "ko'p" tomonda — vazifalar jadvalida, @ManyToOne yonida. Bu Bog'lanishlar: 1-1, 1-N va N-M darsidagi qoidaning o'zi.
3. DataSource va synchronize
3.1 Ulanish
DataSource — TypeORM'ning markaziy obyekti: ulanish ma'lumotlari, entity'lar ro'yxati va sozlamalar. Prisma'dagi PrismaClient, Drizzle'dagi drizzle(pool) o'rnida.
import { DataSource } from "typeorm";
import { Foydalanuvchi, Vazifa, Izoh } from "./entities.js";
const ds = new DataSource({
type: "postgres",
url: process.env["DATABASE_URL"],
entities: [Foydalanuvchi, Vazifa, Izoh],
synchronize: false, // prodakshnda HAR DOIM false
logging: ["query", "schema", "error"],
});
await ds.initialize(); // hovuz ochiladi, metadata quriladi
// … ish …
await ds.destroy(); // hovuz yopiladilogging: ["query"] — har SQL so'rov konsolga chiqadi. O'rganish paytida doim yoqing: ORM nima yuborayotganini ko'rmasdan uni tushunib bo'lmaydi.
3.2 synchronize: true — bo'sh bazada
synchronize: true — "entity'larga qarab bazani o'zing moslashtir". Biz bo'sh bazada ishga tushirdik. TypeORM yuborgan DDL (qatorlar telefon uchun bo'lindi, mazmuni aynan):
CREATE TABLE "foydalanuvchilar" (
"id" SERIAL NOT NULL,
"email" character varying NOT NULL,
"toliq_ism" character varying(100) NOT NULL,
CONSTRAINT "UQ_56243fb8509f85cdc96db3bb380" UNIQUE ("email"),
CONSTRAINT "PK_7a1a5c53a07b3d9e528cc248a9a" PRIMARY KEY ("id"));
CREATE TABLE "vazifalar" (
"id" SERIAL NOT NULL, "matn" text NOT NULL,
"bajarildi" boolean NOT NULL DEFAULT false,
"yaratilgan_vaqt" TIMESTAMP WITH TIME ZONE NOT NULL
DEFAULT now(),
"egasi_id" integer NOT NULL,
CONSTRAINT "PK_88a44a87ee9bc6e784a4a61ea38" PRIMARY KEY ("id"));
CREATE INDEX "IDX_8a0fd84031238a1ec27d9f75cd"
ON "vazifalar" ("egasi_id");
ALTER TABLE "vazifalar" ADD CONSTRAINT
"FK_8a0fd84031238a1ec27d9f75cd4" FOREIGN KEY ("egasi_id")
REFERENCES "foydalanuvchilar"("id")
ON DELETE CASCADE ON UPDATE NO ACTION;Uch narsaga qarang:
SERIALvainteger— 26-qismda bizbigint GENERATED ALWAYS AS IDENTITYni tanlagan edik (Primary key tanlovi). TypeORM sukut bo'yicha eskiSERIALni oladi.- Cheklov nomlari — hash:
UQ_56243fb8…,FK_8a0fd840…. Xato xabarida shu ko'rinadi. 26-qismdagivazifalar_egasi_id_fkeykabi o'qiladigan nom yo'q. email—character varying(uzunliksiz).stringturi uchun TypeORM tanlovi.
Hammasi START TRANSACTION … COMMIT ichida bajarildi (logda ko'rinadi). Bo'sh bazada bu qulay: bir buyruqda jadvallar tayyor.
4. Repository: CRUD
4.1 save va find
Repository (ombor) — bitta entity bilan ishlash uchun metodlar to'plami. 25-qismdagi o'z VazifaOmbori imizga o'xshaydi, faqat tayyor.
const fRepo = ds.getRepository(Foydalanuvchi);
const vRepo = ds.getRepository(Vazifa);
const ali = fRepo.create({
email: "ali@example.com", toliqIsm: "Ali Valiyev" });
await fRepo.save(ali);
console.log("--- ali.id:", ali.id);
await vRepo.save([
{ matn: "Bozordan non olish", egasi: ali },
{ matn: "Hisobot yozish", egasi: ali, bajarildi: true },
]);create() faqat obyekt yasaydi (bazaga bormaydi). save() — yozadi va bazadan qaytgan id ni obyektga qo'yadi. Log (bo'lingan):
query: START TRANSACTION
query: INSERT INTO "foydalanuvchilar"("email", "toliq_ism")
VALUES ($1, $2) RETURNING "id"
-- PARAMETERS: ["ali@example.com","Ali Valiyev"]
query: COMMIT
--- ali.id: 1Qiymatlar $1, $2 parametr sifatida ketdi — SQL injection'dan himoya (Parametrli so'rovlar). Ikki vazifa esa bitta INSERT … VALUES (…), (…) bilan yozildi.
save() ning bir nozik joyi bor: obyektda id bo'lsa, TypeORM avval uni SELECT qiladi, keyin INSERT yoki UPDATE ni tanlaydi. Qulay, lekin bitta yozuv uchun ikki so'rov. Aniq maqsad bo'lsa — insert() yoki update().
4.2 find va where
find — obyekt shaklidagi so'rov:
const royxat = await vRepo.find({
where: { egasi: { id: ali.id }, bajarildi: false },
order: { id: "ASC" },
take: 10,
});
console.log(royxat);Natija:
[
Vazifa {
id: 1,
matn: 'Bozordan non olish',
bajarildi: false,
yaratilganVaqt: 2026-10-25T09:28:35.473Z,
egasi: undefined,
izohlar: undefined
}
]Natija — oddiy obyekt emas, Vazifa klassining nusxasi. egasi: undefined — relation yuklanmagan (so'ralmagan).
Logga qarasak, kutilmagan narsa bor. TypeORM bitta emas, ikkita so'rov yubordi:
-- 1) faqat id'lar, DISTINCT ichki so'rov bilan
SELECT DISTINCT "distinctAlias"."Vazifa_id" AS "ids_Vazifa_id", …
FROM (SELECT … FROM "vazifalar" "Vazifa"
INNER JOIN "foydalanuvchilar" "Vazifa__Vazifa_egasi" ON …
WHERE … ) "distinctAlias"
ORDER BY "distinctAlias"."Vazifa_id" ASC, "Vazifa_id" ASC
LIMIT 10
-- 2) shu id'lar bo'yicha to'liq qatorlar
SELECT … FROM "vazifalar" "Vazifa" INNER JOIN …
WHERE ( … ) AND ( "Vazifa"."id" IN (1) )
ORDER BY "Vazifa"."id" ASCSabab: where relation orqali (egasi: { id }) — demak JOIN. JOIN + take bo'lganda TypeORM "bitta vazifa bir necha qatorga ko'payib ketmasin" deb ehtiyot bo'ladi: avval noyob id'larni oladi, keyin ularni yuklaydi. Kanon o'lchovida aynan shu sabab TypeORM'ning relation savoli 2 SQL bilan 3.78 ms oldi (pg — 1.34 ms, bitta so'rov).
4.3 Operatorlar, update va delete
Tenglikdan boshqa shartlar — maxsus funksiyalar bilan: Like, ILike, In, IsNull, MoreThan, Between, Not.
import { Like } from "typeorm";
console.log(await vRepo.countBy({ matn: Like("%yozish%") }));
console.log(await vRepo.update(
{ matn: "Hisobot yozish" }, { bajarildi: false }));
console.log(await vRepo.delete({ matn: "Hisobot yozish" }));Natija:
1
UpdateResult { generatedMaps: [], raw: [], affected: 1 }
DeleteResult { raw: [], affected: 1 }affected — nechta qator o'zgardi. update() va delete() ga bo'sh shart ({}) berib ko'rmang — hamma qatorga ta'sir qilishi mumkin; UPDATE va DELETE xavfsiz qoidalari bu yerda ham amal qiladi.
Tekshirib ko'ring:
vRepo.find({ where: { bajarildi: false }, take: 10 })(relation shartisiz) ham ikki so'rov yuboradimi?
Javob
Yo'q. Ikki bosqichli DISTINCT faqat JOIN bo'lganda kerak — JOIN qatorlarni ko'paytirishi mumkin. Shartda ham, relations da ham relation bo'lmasa, oddiy bitta SELECT … LIMIT 10 ketadi.
5. Relation'lar: relations, eager, lazy
5.1 relations — so'raganda yuklash
const f = await fRepo.findOne({
where: { email: "ali@example.com" },
relations: { vazifalar: true },
});
console.log(JSON.stringify(f));Natija:
{"id":1,"email":"ali@example.com","toliqIsm":"Ali Valiyev","vazifalar":[{"id":1,"matn":"Bozordan non olish","bajarildi":false,"yaratilganVaqt":"2026-10-25T09:28:35.473Z"},{"id":2,"matn":"Hisobot yozish","bajarildi":true,"yaratilganVaqt":"2026-10-25T09:28:35.473Z"}]}TypeORM LEFT JOIN qildi va natijani daraxtga yig'di. Bu yerda ham findOne (= LIMIT 1) + relation — yana ikki so'rov.
5.2 eager: true — doim yuklash
Relation'ga eager: true qo'ysangiz, find uni har safar o'zi yuklaydi. Biz Vazifa.egasi ga qo'yib ko'rdik:
@ManyToOne(() => Foydalanuvchi, (f) => f.vazifalar, {
nullable: false, onDelete: "CASCADE", eager: true,
})vRepo.find() natijasi:
[{"id":3,"matn":"Non olish","bajarildi":false,"yaratilganVaqt":"2026-10-25T09:31:51.773Z","egasi":{"id":1,"email":"ali@example.com","toliqIsm":"Ali Valiyev"}}]So'rovda INNER JOIN "foydalanuvchilar" paydo bo'ldi — biz so'ramagan bo'lsak ham. Endi har vazifalar ro'yxati foydalanuvchini ham tortadi. Ikki xavf:
- Ortiqcha ma'lumot. 1 000 vazifa — 1 000 marta bir xil foydalanuvchi.
- Zanjir.
Foydalanuvchida hameagerrelation bo'lsa — u ham tortiladi.
Qiziq tafsilot: o'sha entity bilan vRepo.createQueryBuilder("v").getMany() — egasi siz qaytdi. QueryBuilder eager ni e'tiborsiz qoldiradi. Bir xil entity ikki usulda ikki xil shakl qaytaradi — eski loyihalarda chalkash xatolarning manbai.
5.3 lazy — Promise orqali
Uchinchi variant — lazy relation: xususiyat turi Promise<Foydalanuvchi> qilib yoziladi va unga birinchi murojaatda alohida so'rov ketadi (await vazifa.egasi). Bu biz N+1 muammosi darsida ko'rgan tuzoqning to'g'ridan-to'g'ri yo'li: siklda har vazifa uchun bitta so'rov. Lazy relation'ni biz sinamadik; TypeORM hujjatlari ham uni "eksperimental" deb belgilaydi.
Xulosa jadvali:
| Usul | Qachon yuklanadi | Tavsiya |
|---|---|---|
relations: {…} |
Faqat so'raganda | Asosiy yo'l |
eager: true |
find da doim |
Kamdan-kam, kichik ma'lumotnomalar uchun |
lazy (Promise<T>) |
Birinchi await da |
N+1 xavfi — qochish kerak |
6. QueryBuilder
find ning imkoniyati cheklangan: guruhlash, murakkab JOIN, subquery. Shunda QueryBuilder — SQL'ni zanjir metodlar bilan yig'ish. Kysely va Knex'ga o'xshaydi (Kysely va Knex), faqat entity nomlari bilan.
const qb = vRepo.createQueryBuilder("v")
.leftJoin("v.egasi", "f")
.select(["v.id", "v.matn", "f.toliqIsm"])
.where("f.email = :email", { email: "ali@example.com" })
.andWhere("v.matn ILIKE :q", { q: "%non%" })
.orderBy("v.id", "DESC");
console.log(qb.getSql());
console.log(JSON.stringify(await qb.getMany()));getSql() — SQL'ni bazaga yubormasdan ko'rsatadi (bo'lingan):
SELECT "v"."id" AS "v_id", "v"."matn" AS "v_matn",
"f"."toliq_ism" AS "f_toliq_ism"
FROM "vazifalar" "v"
LEFT JOIN "foydalanuvchilar" "f" ON "f"."id"="v"."egasi_id"
WHERE "f"."email" = $1 AND "v"."matn" ILIKE $2
ORDER BY "v_id" DESCNatija:
[{"id":1,"matn":"Bozordan non olish","egasi":{"toliqIsm":"Ali Valiyev"}}]Muhim qoida: :email — nomli parametr, qiymat alohida obyektda. TypeORM uni $1 ga aylantiradi. Lekin .where(\f.email = '${email}'`)deb yozsangiz — bu SQL injection, xuddi$queryRawUnsafe` dagidek (Prisma: raw SQL).
Yana bir kuzatuv: biz select da faqat f.toliqIsm ni so'radik, haqiqiy so'rovda esa "f"."id" AS "f_id" ham qo'shildi. TypeORM obyektni yig'ish uchun birlamchi kalitni o'zi qo'shadi.
7. Migratsiyalar va synchronize xavfi
7.1 synchronize mavjud bazada
Endi bazada ma'lumot bor. Entity'da matn ni sarlavha ga qayta nomladik. synchronize nima qilishini avval faqat ko'rib chiqamiz — createSchemaBuilder().log() (bazaga yozmaydi):
renaming column "matn" in "vazifalar" to "sarlavha"
ALTER TABLE "vazifalar" RENAME COLUMN "matn" TO "sarlavha"Yaxshi: TypeORM 1.1 bitta ustun nomi o'zgarganini "rename" deb tanidi, ma'lumot qoldi. Endi esa turini o'zgartirdik: @Column({ type: "text" }) → @Column({ length: 50, nullable: true }). Reja:
columns changed in "vazifalar". updating: sarlavha
ALTER TABLE "vazifalar" DROP COLUMN "sarlavha"
ALTER TABLE "vazifalar" ADD "sarlavha" character varying(50)ALTER COLUMN … TYPE emas — DROP COLUMN + ADD. synchronize: true bilan ishga tushirdik, keyin bazaga qaradik:
id | sarlavha
----+----------
1 |
(1 row)"Bozordan non olish" yo'qoldi. Hech qanday ogohlantirish, savol yoki xato yo'q edi.
Birinchi urinishda ustun NOT NULL edi (nullable siz). U holda ADD … NOT NULL mavjud qatorlarda yiqildi (column "sarlavha" of relation "vazifalar" contains null values), tranzaksiya qaytdi va ma'lumot saqlandi. Ya'ni ma'lumotni sizni "omad" saqlaydi, TypeORM emas.
Uch tajribani yonma-yon qo'yamiz:
| Entity o'zgarishi | synchronize nima qildi |
Ma'lumot |
|---|---|---|
matn → sarlavha (nom) |
RENAME COLUMN |
saqlandi |
tur o'zgardi, nullable: true |
DROP COLUMN + ADD |
yo'qoldi |
tur o'zgardi, NOT NULL |
DROP + ADD → xato, ROLLBACK |
tasodifan saqlandi |
Diqqat:
synchronize: true— faqat o'quv va prototip uchun, tashlab yuboriladigan bazada. Hamkasbingiz bilan bo'lishilgan dev bazada, staging va prodakshnda — hech qachon. TypeORM hujjati ham shuni aytadi.
7.2 migration:generate
To'g'ri yo'l — migratsiya: sxema o'zgarishi alohida faylda, kod bilan birga Git'da. Migratsiya tushunchasini keyingi darslarda chuqur o'rganamiz (Migratsiya tushunchasi); bu yerda TypeORM'ning o'z vositasini ko'ramiz.
CLI uchun DataSource alohida faylda eksport qilinadi (src/data-source.ts):
import { DataSource } from "typeorm";
import { Foydalanuvchi, Vazifa, Izoh } from "./entities.js";
export default new DataSource({
type: "postgres",
url: process.env["DATABASE_URL"],
entities: [Foydalanuvchi, Vazifa, Izoh],
migrations: ["dist/migrations/*.js"],
synchronize: false,
});Bo'sh bazaga qarab migratsiya yasaymiz (avval tsc):
npx tsc -p .
npx typeorm migration:generate -d dist/data-source.js \
src/migrations/BoshlangichMigration …\src\migrations/1792920661943-Boshlangich.ts has been generated successfully.TypeORM entity'lar bilan bazani solishtirdi va farqni SQL'ga aylantirdi. Fayl (qisqartirildi):
export class Boshlangich1792920661943
implements MigrationInterface {
name = 'Boshlangich1792920661943'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "foydalanuvchilar" …`);
await queryRunner.query(`CREATE TABLE "vazifalar" …`);
// … indeks, izohlar, 2 ta FOREIGN KEY
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "izohlar" …`);
// … teskari tartibda
await queryRunner.query(`DROP TABLE "foydalanuvchilar"`);
}
}up — oldinga, down — orqaga. Nomdagi raqam — Date.now(), migratsiyalar tartibi shundan.
Maslahat: Generatsiya qilingan faylni har doim o'qing. Agar entity o'zgarishi turini o'zgartirish bo'lsa,
generatehamsynchronizedagiDROP COLUMN+ADDni yozishi mumkin. Farqi — endi buni review'da ko'rasiz va qo'ldaALTER COLUMN … TYPE … USINGga almashtirasiz.
7.3 run, show, revert
npx typeorm migration:show -d dist/data-source.js
npx typeorm migration:run -d dist/data-source.js
npx typeorm migration:show -d dist/data-source.jsNatija (asosiy qatorlar):
[ ] Boshlangich1792920661943
1 migrations are new migrations must be executed.
query: START TRANSACTION
query: CREATE TABLE "foydalanuvchilar" …
query: INSERT INTO "migrations"("timestamp", "name") VALUES ($1, $2) -- PARAMETERS: [1792920661943,"Boshlangich1792920661943"]
Migration Boshlangich1792920661943 has been executed successfully.
query: COMMIT
[X] 1 Boshlangich1792920661943TypeORM bazada migrations jadvalini yaratdi va qo'llangan migratsiyani unga yozdi. Bu — holat jadvali: keyingi run qaysilari qo'llanganini shu yerdan biladi. migration:revert oxirgi migratsiyaning down ini bajaradi va yozuvni o'chiradi:
Now reverting it...
query: START TRANSACTION
query: DROP TABLE "izohlar"
query: DROP TABLE "vazifalar"
query: DROP TABLE "foydalanuvchilar"
query: DELETE FROM "migrations" WHERE "timestamp" = $1 AND "name" = $2 -- PARAMETERS: [1792920661943,"Boshlangich1792920661943"]
Migration Boshlangich1792920661943 has been reverted successfully.
query: COMMITYana ikki foydali bayroq (CLI yordamidan, 1.1.2):
migration:run -t each— har migratsiya alohida tranzaksiyada. Sukut (all) — hammasi bitta tranzaksiyada.CREATE INDEX CONCURRENTLYkerak bo'lsa, migratsiya klassidatransaction = falseva-t each(yokimigrationsTransactionMode).migration:run --fake— migratsiyani bajarmasdan "qo'llandi" deb yozadi. Mavjud bazani migratsiyaga o'tkazishda (baseline) kerak — Prisma Migrate chuqur darsida xuddi shu muammoni ko'ramiz.
Tekshirib ko'ring:
migration:revertni prodakshnda ishga tushirish xavfsizmi?
Javob
Ko'pincha yo'q. Bizning down — DROP TABLE: ma'lumot bilan birga ketadi. down faqat sxemani qaytaradi, o'chgan ma'lumotni qaytarmaydi. Shuning uchun ko'p jamoalar prodakshnda faqat oldinga yuradi (xatoni yangi migratsiya bilan tuzatadi). Bu yondashuvni Migratsiya tushunchasi darsida ko'ramiz.
8. Ko'p uchraydigan xatolar
1. synchronize: true prodakshnda. Yuqorida ko'rdik: tur o'zgarishi — DROP COLUMN. Tuzatish: synchronize: false, faqat migratsiyalar. Muhit o'zgaruvchisiga bog'lab qo'yish ham xavfli (synchronize: NODE_ENV !== "production") — staging'da NODE_ENV noto'g'ri bo'lsa, o'sha zahoti ma'lumot ketadi.
2. column distinctAlias.Vazifa_tartib does not exist. Kanon o'lchovida uchragan: relation + take + order da saralash ustuni select ichida yo'q edi. TypeORM yuqoridagi ikki bosqichli DISTINCT so'rovini yasaydi va tashqi ORDER BY ichki so'rovda yo'q ustunga ishora qiladi. Tarjimasi: "distinctAlias.Vazifa_tartib ustuni mavjud emas". Tuzatish: saralash ustunini select ga qo'shing.
3. Data type "Object" … is not supported. Biz @Column() izoh: string | null; yozib ishga tushirdik:
DataTypeNotSupportedError | Data type "Object" in "Sinov.izoh" is not supported by "postgres" database.Tarjimasi: "Sinov.izoh dagi Object turini postgres bazasi qo'llamaydi". Birlashma tur (string | null) metama'lumotda Object bo'lib qoladi — TypeORM ustun turini topa olmaydi. Tuzatish: @Column({ type: "text", nullable: true }) — turini aniq yozing.
4. Dekoratorli entity node src/…ts da ishlamaydi. Type stripping dekoratorni bajarmaydi (kanon topilmasi). Tuzatish: tsc bilan kompilyatsiya yoki EntitySchema.
5. save() ni hamma joyda ishlatish. Bitta yangilash uchun SELECT + UPDATE. Ko'p yozuvli ommaviy yangilashda — update() yoki QueryBuilder .update().
6. Cheklov nomlari hash. UQ_56243fb8… xatosini ko'rib "qaysi cheklov?" deb qolasiz. Tuzatish: @Unique("foydalanuvchilar_email_key", ["email"]), @Index("…"), foreignKeyConstraintName — nomni o'zingiz bering.
9. Mashqlar
Mashq yechimlarining SQL qismi tekshirilgan (biz ishga tushirdik, PostgreSQL 17.6).
1-mashq (oson)
Entity'da @Column({ length: 100 }) toliqIsm bor. Siz uni @Column({ length: 150 }) ga o'zgartirdingiz va migration:generate qildingiz. Generatsiya qilingan faylda qanday buyruqni ko'rishni xohlaysiz va qanday buyruq chiqsa, faylni qo'lda tuzatasiz?
Yechim
Xohlaganimiz: ALTER TABLE "foydalanuvchilar" ALTER COLUMN "toliq_ism" TYPE character varying(150). varchar uzunligini oshirish PostgreSQL'da jadvalni qayta yozmaydi — bir zumda (ALTER TABLE).
Tuzatish kerak bo'ladigani: DROP COLUMN "toliq_ism" + ADD "toliq_ism" character varying(150). TypeORM tur yoki uzunlik o'zgarishini ba'zan shunday yozadi — biz synchronize da aynan shuni ko'rdik. DROP ko'rsangiz — ikki qatorni bitta ALTER COLUMN … TYPE bilan almashtiring, down ni ham mos ravishda tuzating.
2-mashq (o'rta)
Har foydalanuvchining bajarilmagan vazifalari sonini qaytaradigan QueryBuilder yozing (toliqIsm, soni), soni bo'yicha kamayish tartibida, faqat soni > 0 bo'lganlar. Keyin TypeORM yuboradigan SQL'ni o'zingiz yozib, bazada sinang.
Ishora: .select("f.toliqIsm", "toliqIsm"), .addSelect("COUNT(v.id)", "soni"), .groupBy("f.id"), .having(…), natija — .getRawMany() (agregat entity emas, xom qator). Guruhlash — Agregatlar, GROUP BY va HAVING.
Yechim
const r = await fRepo.createQueryBuilder("f")
.innerJoin("f.vazifalar", "v", "v.bajarildi = :b",
{ b: false })
.select("f.toliqIsm", "toliqIsm")
.addSelect("COUNT(v.id)::int", "soni")
.groupBy("f.id")
.having("COUNT(v.id) > 0")
.orderBy("soni", "DESC")
.getRawMany();innerJoin shartiga bajarildi = false qo'yildi — shuning uchun vazifasi yo'q foydalanuvchi natijaga kirmaydi (HAVING ham shuni kafolatlaydi). ::int — COUNT bigint qaytaradi, pg uni satr qiladi; int ga aylantirsak, JS'da son keladi.
Ekvivalent SQL (TypeORM yaratgan jadvallar bilan sinash uchun):
CREATE TABLE foydalanuvchilar (id serial PRIMARY KEY,
email varchar NOT NULL UNIQUE,
toliq_ism varchar(100) NOT NULL);
CREATE TABLE vazifalar (id serial PRIMARY KEY,
matn text NOT NULL,
bajarildi boolean NOT NULL DEFAULT false,
egasi_id int NOT NULL REFERENCES foydalanuvchilar
ON DELETE CASCADE);
INSERT INTO foydalanuvchilar (email, toliq_ism) VALUES
('ali@example.com', 'Ali Valiyev'),
('malika@example.com', 'Malika Karimova'),
('jasur@example.com', 'Jasur Aliyev');
INSERT INTO vazifalar (matn, bajarildi, egasi_id) VALUES
('Non olish', false, 1), ('Hisobot', true, 1),
('Kitob', false, 1), ('Sport', false, 2),
('Xat', true, 3);
SELECT f.toliq_ism AS "toliqIsm",
COUNT(v.id)::int AS soni
FROM foydalanuvchilar f
JOIN vazifalar v ON v.egasi_id = f.id AND v.bajarildi = false
GROUP BY f.id
HAVING COUNT(v.id) > 0
ORDER BY soni DESC;Natija:
CREATE TABLE
CREATE TABLE
INSERT 0 3
INSERT 0 5
toliqIsm | soni
-----------------+------
Ali Valiyev | 2
Malika Karimova | 1
(2 rows)Nima bo'ldi? Alining uchta vazifasidan ikkitasi bajarilmagan, Malikaning bittasi. Jasurning yagona vazifasi bajarilgan — u ro'yxatda yo'q.
3-mashq (qiyin)
vazifalar.matn ga indeks kerak, lekin jadval katta va prodakshnda yozuvlar to'xtamasligi kerak. TypeORM migratsiyasini qo'lda yozing: up — CREATE INDEX CONCURRENTLY, down — DROP INDEX CONCURRENTLY. Nima uchun migratsiyaga qo'shimcha sozlama kerak va qaysi?
Yechim
import { MigrationInterface, QueryRunner } from "typeorm";
export class MatnIndeksi1792921000000
implements MigrationInterface {
name = "MatnIndeksi1792921000000";
// CONCURRENTLY tranzaksiya ichida ishlamaydi
transaction = false;
public async up(q: QueryRunner): Promise<void> {
await q.query(`CREATE INDEX CONCURRENTLY IF NOT EXISTS
vazifalar_matn_idx ON vazifalar (matn)`);
}
public async down(q: QueryRunner): Promise<void> {
await q.query(
`DROP INDEX CONCURRENTLY IF EXISTS vazifalar_matn_idx`);
}
}Ishga tushirish: npx typeorm migration:run -d dist/data-source.js -t each.
Nega: PostgreSQL CREATE INDEX CONCURRENTLY ni tranzaksiya blokida rad etadi (cannot run inside a transaction block). TypeORM sukut bo'yicha hamma migratsiyani bitta tranzaksiyaga o'raydi (all). transaction = false faqat each yoki none rejimida ishlaydi (MigrationInterface izohida shunday yozilgan). IF NOT EXISTS — CONCURRENTLY yarmida uzilsa, INVALID indeks qoladi; uni keyingi darslarda ko'ramiz (Xavfli migratsiyalar va qulflar).
Bu kodni biz TypeORM bilan ishga tushirmadik — transaction xususiyati 1.1.2 ning tur ta'rifidan va CLI kodidan olingan. Xuddi shu muammoning boshqa vositadagi haqiqiy natijasini ORM'siz migratsiya vositalari darsida ko'rasiz.
10. Loyiha qadami
Bu dars vazifalar-api ga kod qo'shmaydi. Kanon qarori o'lchovga tayanadi: API'da Prisma asosiy, pg — taqqoslash va xom SQL uchun. TypeORM — eski loyihalarda uchraydi, yangi loyihaga tavsiya etilmaydi.
Kanon TypeORM'ni o'sha katta bazada (500 000 vazifa, egasi 77, mediana 30) boshqa kutubxonalar bilan bir xil savollarda o'lchagan:
- pg (raw)1 SQL, json_agg1,34 ms
- Drizzle1 SQL1,61 ms
- Sequelize1 SQL2,03 ms
- Kysely1 SQL2,07 ms
- Knex2 SQL2,3 ms
- TypeORM2 SQL3,78 ms
Manba: IlmHamroh kanon o'lchovi t29-orm: PostgreSQL 18.6, Node 24.21.0, baza katta (500 000 vazifa), mediana 30, 2026-10-25
TypeORM eng sekin chiqdi (ikki so'rov sababli) — lekin farq millisekundlarda. Paket hajmi esa sezilarli: TypeORM 28 MB, pg 179 KB. Tanlovni hal qiladigan narsa — tezlik emas, xavfsizlik va qulaylik. Buni ORM tanlash qo'llanmasi da jamlaymiz.
O'zingiz sinamoqchi bo'lsangiz — alohida tajriba papkasida (vazifalar-api ga emas): npm i typeorm@1.1.2 pg@8.23.1, bo'sh baza, synchronize: true faqat birinchi ishga tushirishda. Commit yo'q.
11. Real ishda
- NestJS loyihalari.
@nestjs/typeormorqaliTypeOrmModule.forFeature([Vazifa]),@InjectRepository(Vazifa). NestJS'ni 30-qismda o'rganamiz; u yerda ham shu entity va repository tushunchalari ishlaydi. - Kod review. Eski TypeORM loyihasiga kelsangiz, birinchi tekshiradigan narsa:
synchronizeqayerda yoqilgan? Ikkinchisi:eager: truelar qayerda? - Active Record uslubi. Ba'zi loyihalarda entity
BaseEntitydan meros oladi vaVazifa.find()kabi statik metodlar ishlatiladi. Biz ko'rgan uslub — Data Mapper (repository orqali); ikkalasini ham o'qiy olasiz. - Intervyuda: "
synchronizenima va nega xavfli?", "eager va lazy farqi?", "QueryBuilder qachon kerak?" — javobdaDROP COLUMNtajribasini va N+1 ni tilga oling.
Xulosa
- TypeORM — dekoratorli entity'lar (
@Entity,@Column,@ManyToOne), legacy dekoratorlar vaemitDecoratorMetadatakerak; Node type stripping ularni bajarmaydi. DataSource— ulanish va sozlama;Repository—find,save,update,delete;QueryBuilder— murakkab so'rovlar, nomli parametrlar bilan.- Relation +
take— ikki so'rovliDISTINCTnaqsh; kanon o'lchovida 3.78 ms (pg 1.34 ms). eagerdoim yuklaydi (QueryBuilder'dan tashqari), lazy — N+1 yo'li; asosiy usul —relations.synchronize: truetur o'zgarishidaDROP COLUMN+ADDqildi va ma'lumot yo'qoldi — prodakshnda faqat migratsiya.migration:generate→ faylni o'qing →migration:run; holatmigrationsjadvalida; sukut — hamma migratsiya bitta tranzaksiyada.
Keyingi dars: Sequelize — eski Express loyihalarining ORM'i: Model.init, associations, include, Op va sequelize-cli migratsiyalari.
Manbalar
- TypeORM hujjati — Entities, Relations, Eager and Lazy Relations, Migrations: https://typeorm.io/docs/
- TypeORM 1.1.2 paket kodi:
MigrationInterface.transaction,migration:run --transaction,--fake(2026-10-25 holati). - NestJS hujjati — Database (TypeORM integratsiyasi): https://docs.nestjs.com/techniques/database
- IlmHamroh kanon o'lchovi t29-orm (PostgreSQL 18.6, 2026-10-25); darsdagi TypeORM chiqishlari — biz ishga tushirdik (TypeORM 1.1.2, PostgreSQL 17.6, Windows 11), 2026-10-25.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!