Sequelize ORM — model, migration, association
Muammodan boshlaylik
mysql2 bilan ishladik (55-56 darslar). Hammasi aniq va nazorat ostida. Lekin loyiha o'sgach:
- Har bir jadval uchun bir xil CRUD funksiyalarini qayta-qayta yozish
- Buyurtmani mijoz va mahsulotlari bilan olish uchun qo'lda JOIN va natijani ichma-ich ob'ektga yig'ish
- Jadval tuzilmasidagi o'zgarishlar — qo'lda
ALTER TABLEva "serverda bajarishni unutdim"
ORM (Object-Relational Mapping) — jadvallarni JavaScript classlari (modellar) sifatida ifodalaydi. SQL ni ko'p hollarda ORM o'zi yozadi. Sequelize — Node.js dagi eng eski va keng tarqalgan ORM lardan biri.
O'rnatish
npm install sequelize mysql2
npm install --save-dev sequelize-cli
npx sequelize-cli init
init papkalar yaratadi:
config/config.json ← ulanish sozlamalari
models/ ← modellar
migrations/ ← jadval o'zgarishlari
seeders/ ← boshlang'ich ma'lumotlar
config/config.json (parollarni muhit o'zgaruvchilaridan olish uchun uni config.js ga aylantirish tavsiya etiladi):
{
"development": {
"username": "dokon_app",
"password": null,
"database": "dokon_dev",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
Migratsiyalar — jadval tuzilmasi kod sifatida
npx sequelize-cli migration:generate --name create-mijozlar
migrations/20261104120000-create-mijozlar.js:
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('mijozlar', {
id: { type: Sequelize.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true },
ism: { type: Sequelize.STRING(100), allowNull: false },
email: { type: Sequelize.STRING(255), allowNull: false, unique: true },
telefon: { type: Sequelize.STRING(20) },
shahar: { type: Sequelize.STRING(50) },
created_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') },
updated_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') },
});
},
async down(queryInterface) {
await queryInterface.dropTable('mijozlar');
},
};
npx sequelize-cli db:migrate # barcha yangi migratsiyalarni bajarish
npx sequelize-cli db:migrate:undo # oxirgisini bekor qilish (down)
npx sequelize-cli db:migrate:status # qaysilari bajarilgan
up— o'zgarishni qo'llash,down— qaytarish- Sequelize bajarilganlarni
SequelizeMetajadvalida kuzatadi - Migratsiya fayllari Git da — har bir dasturchi va server bir xil tuzilmaga ega (8-dars)
Ustun qo'shish:
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('mijozlar', 'tugilgan_kun', { type: Sequelize.DATEONLY });
await queryInterface.addIndex('mijozlar', ['shahar']);
},
async down(queryInterface) {
await queryInterface.removeIndex('mijozlar', ['shahar']);
await queryInterface.removeColumn('mijozlar', 'tugilgan_kun');
},
[!WARNING]
sequelize.sync()— modellardan jadvallarni avtomatik yaratadi. O'rganish va testlar uchun qulay, lekin production da ishlatmang:sync({ alter: true })ma'lumotni yo'qotishi mumkin,sync({ force: true })esa jadvallarni o'chirib qayta yaratadi. Production — faqat migratsiyalar.
Modellar
models/mijoz.js:
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class Mijoz extends Model {
static associate(models) {
Mijoz.hasMany(models.Buyurtma, { foreignKey: 'mijoz_id', as: 'buyurtmalar' });
}
}
Mijoz.init({
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true },
ism: { type: DataTypes.STRING(100), allowNull: false, validate: { len: [2, 100] } },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true, validate: { isEmail: true } },
telefon: DataTypes.STRING(20),
shahar: DataTypes.STRING(50),
}, {
sequelize,
modelName: 'Mijoz',
tableName: 'mijozlar',
underscored: true, // createdAt → created_at
});
return Mijoz;
};
validate— ilova darajasidagi tekshiruv (bazadagi cheklovlarga qo'shimcha, 6-dars)tableName— o'zbekcha nomlar uchun aniq ko'rsatish kerak (Sequelize standart holatda inglizcha ko'plik yasaydi:Mijoz→Mijozs)underscored: true—snake_caseustunlar (49-dars)
CRUD
const { Mijoz, Mahsulot } = require('./models');
const { Op } = require('sequelize');
// CREATE
const m = await Mijoz.create({ ism: 'Dilshod', email: 'dilshod@mail.uz', shahar: 'Toshkent' });
console.log(m.id);
// READ
const bitta = await Mijoz.findByPk(1);
const email = await Mijoz.findOne({ where: { email: 'aziz@mail.uz' } });
const toshkentliklar = await Mijoz.findAll({
where: { shahar: 'Toshkent' },
attributes: ['id', 'ism', 'email'], // SELECT * emas
order: [['ism', 'ASC']],
limit: 20,
offset: 0,
});
// Murakkab shartlar — Op operatorlari
const arzonlar = await Mahsulot.findAll({
where: {
narx: { [Op.between]: [100000, 5000000] },
soni: { [Op.gt]: 0 },
nom: { [Op.like]: '%Pro%' },
[Op.or]: [{ toifa_id: 1 }, { toifa_id: 3 }],
},
});
// UPDATE
await Mijoz.update({ shahar: 'Buxoro' }, { where: { id: 4 } });
// yoki
bitta.shahar = 'Buxoro';
await bitta.save();
// DELETE
await Mijoz.destroy({ where: { id: 4 } });
// Hisoblash
const soni = await Mijoz.count({ where: { shahar: 'Toshkent' } });
const jami = await Mahsulot.sum('soni');
Barcha qiymatlar Sequelize ichida prepared statements orqali yuboriladi — SQL injection xavfi yo'q (agar sequelize.literal() yoki sequelize.query() ga o'zgaruvchi qo'shmasangiz).
Association — munosabatlar
// models/buyurtma.js — associate ichida
Buyurtma.belongsTo(models.Mijoz, { foreignKey: 'mijoz_id', as: 'mijoz' });
Buyurtma.hasMany(models.BuyurtmaQatori, { foreignKey: 'buyurtma_id', as: 'qatorlar' });
// models/mahsulot.js
Mahsulot.belongsTo(models.Toifa, { foreignKey: 'toifa_id', as: 'toifa' });
Mahsulot.belongsToMany(models.Teg, {
through: 'mahsulot_teglar',
foreignKey: 'mahsulot_id',
otherKey: 'teg_id',
as: 'teglar',
});
// models/foydalanuvchi.js
Foydalanuvchi.hasOne(models.Profil, { foreignKey: 'foydalanuvchi_id', as: 'profil' });
| Sequelize | Munosabat | Dars |
|---|---|---|
belongsTo |
Foreign key shu jadvalda (buyurtma → mijoz) | 18 |
hasMany |
Foreign key boshqa jadvalda, ko'p (mijoz → buyurtmalar) | 18 |
hasOne |
Foreign key boshqa jadvalda, bitta (foydalanuvchi → profil) | 20 |
belongsToMany |
Oraliq jadval orqali (mahsulot ↔ teglar) | 19 |
Eager loading — include
const buyurtma = await Buyurtma.findByPk(3, {
include: [
{ model: Mijoz, as: 'mijoz', attributes: ['ism', 'telefon'] },
{
model: BuyurtmaQatori, as: 'qatorlar',
include: [{ model: Mahsulot, as: 'mahsulot', attributes: ['nom'] }],
},
],
});
console.log(buyurtma.mijoz.ism); // Malika Rahimova
console.log(buyurtma.qatorlar[0].mahsulot.nom); // MacBook Air M3
Sequelize JOIN yozadi va natijani ichma-ich ob'ektlarga yig'adi. Qo'lda bunday "yig'ish" — o'nlab qator kod.
N+1 tuzog'i
// ❌ N+1 — har bir buyurtma uchun alohida so'rov (18-dars)
const buyurtmalar = await Buyurtma.findAll({ limit: 50 });
for (const b of buyurtmalar) {
const mijoz = await b.getMijoz(); // 50 ta qo'shimcha so'rov!
}
// ✅ include — 1 ta so'rov
const buyurtmalar = await Buyurtma.findAll({ limit: 50, include: [{ model: Mijoz, as: 'mijoz' }] });
Ishlab chiqishda logging: console.log yoqing — Sequelize yuborayotgan har bir SQL ni ko'rasiz.
Tranzaksiyalar
const t = await sequelize.transaction();
try {
const b = await Buyurtma.create({ mijoz_id: 2 }, { transaction: t });
await BuyurtmaQatori.create({ buyurtma_id: b.id, mahsulot_id: 5, soni: 1, narx: 3100000 }, { transaction: t });
await Mahsulot.decrement('soni', { by: 1, where: { id: 5 }, transaction: t });
await t.commit();
} catch (e) {
await t.rollback();
throw e;
}
Har bir so'rovga { transaction: t } uzatishni unutsangiz — u tranzaksiyadan tashqarida bajariladi. Qulayroq — "managed" tranzaksiya, avtomatik commit/rollback bilan:
await sequelize.transaction(async (t) => {
const mahsulot = await Mahsulot.findByPk(5, { lock: t.LOCK.UPDATE, transaction: t }); // FOR UPDATE
if (mahsulot.soni < 1) throw new Error('Omborda yo\'q');
// ...
});
Xom SQL
ORM hamma narsani qila olmaydi. Murakkab hisobotlar, window funksiyalar (29-dars) — xom SQL bilan:
const [natija] = await sequelize.query(
`SELECT m.ism, SUM(q.soni * q.narx) AS jami
FROM mijozlar m JOIN buyurtmalar b ON b.mijoz_id = m.id
JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
WHERE b.holat <> 'bekor' AND m.shahar = :shahar
GROUP BY m.id ORDER BY jami DESC`,
{ replacements: { shahar: 'Toshkent' }, type: QueryTypes.SELECT }
);
replacements — placeholder. Hech qachon ${shahar} qo'shmang.
Nima uchun shunday?
Nima uchun ORM lar ko'p tanqid qilinadi, agar ular bunchalik qulay bo'lsa?
Chunki ular SQL ni yashiradi — va yashirilgan narsa nazoratdan chiqadi. Dasturchi buyurtma.getMijoz() ni oddiy xususiyat kabi ishlatadi va siklda 500 ta so'rov yuborayotganini bilmaydi. Murakkab so'rovlar uchun ORM sintaksisi SQL dan ham chalkashroq. Lekin oddiy CRUD, munosabatlar va migratsiyalar uchun ORM vaqtni sezilarli tejaydi. Muvozanat: ORM — kundalik ish uchun, xom SQL — murakkab hisobotlar uchun, va doim generatsiya qilingan SQL ni ko'rib turing.
Xulosa
Eslab qoling:
- ORM — jadvallar classlar sifatida; SQL ni ko'p hollarda o'zi yozadi
- Migratsiyalar:
up/down,db:migrate; production dasync()emas - Model:
init({...}, { tableName, underscored }),validate - CRUD:
create,findByPk,findAll({ where, attributes, order, limit }),update,destroy Op.gt,Op.like,Op.or— murakkab shartlarbelongsTo,hasMany,hasOne,belongsToMany;include— eager loading, N+1 dan himoya- Tranzaksiya:
sequelize.transaction(async t => ...)+ har so'rovga{ transaction: t } - Xom SQL —
sequelize.query+replacements loggingyoqib, generatsiya qilingan SQL ni kuzating
Keyingi dars
58-dars: Prisma ORM — schema, migration, CRUD