Middleware (Hooks) — pre, post, save, find, remove
Muammodan boshlaylik
User yaratiladigan joylar: ro'yxatdan o'tish, admin panel, seed skript, Google orqali kirish. Har birida:
const xesh = await bcrypt.hash(parol, 12)
await User.create({ ...malumot, parol: xesh })
Bitta joyda unutildi — parol bazaga ochiq matnda tushdi. Yoki: mahsulot nomidan slug yasash, post o'chirilganda uning izohlarini ham o'chirish — bu qoidalar ham kod bo'ylab tarqalgan.
Bunday "har doim shunday bo'lishi kerak" qoidalari model ning o'zida bo'lishi kerak.
Middleware nima?
Middleware (hook) — Mongoose amalidan oldin (pre) yoki keyin (post) avtomatik bajariladigan funksiya.
userSchema.pre('save', async function () {
// saqlashdan oldin
})
userSchema.post('save', function (doc) {
// saqlangandan keyin
})
Hayotiy o'xshatma
Aeroportda yuk topshirish. Siz shunchaki chamadonni lentaga qo'yasiz. "Oldin" — avtomatik tarozi va rentgen (pre). "Keyin" — chamadonga birka yopishtiriladi va sizga SMS keladi (post). Siz har safar o'zingiz tarozi so'rashingiz shart emas — tizim o'zi bajaradi.
Parolni xeshlash — klassik misol
import bcrypt from 'bcrypt'
const userSchema = new Schema({
email: { type: String, required: true, unique: true },
parol: { type: String, required: true, minlength: 8, select: false },
})
userSchema.pre('save', async function () {
if (!this.isModified('parol')) return // parol o'zgarmagan — qayta xeshlamaslik
this.parol = await bcrypt.hash(this.parol, 12)
})
userSchema.methods.parolTogrimi = function (kiritilgan) {
return bcrypt.compare(kiritilgan, this.parol)
}
await User.create({ email: "lola@mail.uz", parol: "maxfiy123" })
// bazada: "$2b$12$Kx..." — avtomatik xeshlangan
isModified('parol') — juda muhim: user ismini o'zgartirib save() qilsangiz, allaqachon xeshlangan parol qayta xeshlanmasin (aks holda user kira olmaydi).
Middleware turlari
| Tur | Hook lar | this |
|---|---|---|
| Document | save, validate, deleteOne ({ document: true }) |
Document |
| Query | find, findOne, updateOne, findOneAndUpdate, deleteMany... |
Query |
| Aggregate | aggregate |
Aggregation obyekti |
| Model | insertMany |
Model |
[!WARNING]
pre('save')faqatsave()vacreate()da ishlaydi.updateOne,findByIdAndUpdate— ishlamaydi. ParolnifindByIdAndUpdate(id, { parol })bilan o'zgartirsangiz — u xeshlanmasdan saqlanadi! Parol o'zgarishini har doimfind+saveorqali qiling yokipre('findOneAndUpdate')hook ini ham yozing.
Slug yasash
mahsulotSchema.pre('validate', function () {
if (this.isModified('nomi') || !this.slug) {
this.slug = this.nomi
.toLowerCase()
.replace(/[ʻʼ'`]/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
})
pre('validate') — validatsiyadan oldin, shuning uchun slug: { required: true } bo'lsa ham ishlaydi.
Query middleware
Avtomatik filtr
// o'chirilgan (soft delete) documentlar hech qachon ko'rinmasin
mahsulotSchema.pre(/^find/, function () {
this.where({ ochirilgan: { $ne: true } })
})
/^find/ — regex: find, findOne, findOneAndUpdate... hammasi uchun. this — query.
Update da updatedAt yoki qo'shimcha maydon
postSchema.pre('findOneAndUpdate', function () {
const update = this.getUpdate()
if (update.sarlavha) {
this.set({ slug: slugYasa(update.sarlavha) })
}
})
Sekin so'rovlarni o'lchash
schema.pre(/^find/, function () {
this._boshlandi = Date.now()
})
schema.post(/^find/, function () {
const ms = Date.now() - this._boshlandi
if (ms > 200) logger.warn({ ms, filtr: this.getFilter() }, 'Sekin so\'rov')
})
Bog'liq ma'lumotlarni o'chirish
postSchema.pre('deleteOne', { document: true, query: false }, async function () {
await mongoose.model('Izoh').deleteMany({ post: this._id })
})
const post = await Post.findById(id)
await post.deleteOne() // ✅ hook ishlaydi — izohlar ham o'chadi
await Post.deleteOne({ _id: id }) // ❌ document hook ishlamaydi (bu query)
Query uchun alohida:
postSchema.pre('deleteMany', async function () {
const postlar = await this.model.find(this.getFilter()).select('_id').lean()
await mongoose.model('Izoh').deleteMany({ post: { $in: postlar.map(p => p._id) } })
})
[!INFO]
remove()metodi Mongoose 7 da olib tashlangan. EndideleteOne()vadeleteMany()hook laridan foydalaning; document hook i uchun{ document: true, query: false }ko'rsating.
post — keyin
userSchema.post('save', function (doc) {
logger.info({ userId: doc._id }, 'User saqlandi')
})
// dublikat xatosini chiroyli xabarga aylantirish
userSchema.post('save', function (err, doc, next) {
if (err.name === 'MongoServerError' && err.code === 11000) {
next(new Error("Bu email allaqachon ro'yxatdan o'tgan"))
} else {
next(err)
}
})
Xato qayta ishlovchi post hook — 3 parametrli (err, doc, next).
Qachon middleware ishlatmaslik
- Email yuborish, tashqi API —
post('save')ichida emas. Saqlash transaction ichida bo'lsa va keyin bekor qilinsa — email baribir ketgan. Navbat (Express kursi, 71-dars) yoki service qatlami - Murakkab biznes mantiq — yashirin "sehr" kodni tushunishni qiyinlashtiradi. Middleware — ma'lumot yaxlitligi uchun: xeshlash, slug, vaqt belgilari, kaskad o'chirish
Tez-tez uchraydigan xatolar
Arrow function
// ❌ — this undefined
userSchema.pre('save', async () => { this.parol = ... })
isModified siz xeshlash
Har bir save() da parol qayta xeshlanadi va user kira olmay qoladi.
Update da save hook ini kutish
findByIdAndUpdate — pre('save') ni chaqirmaydi.
Xulosa
- Middleware — amaldan oldin (
pre) yoki keyin (post) avtomatik kod - Document hook lar (
save,validate) —this= document; faqatsave/createda - Query hook lar (
find,findOneAndUpdate,deleteMany) —this= query;/^find/regex - Parol xeshlash —
pre('save')+isModified; update da ishlamasligini unutmang - Kaskad o'chirish —
pre('deleteOne', { document: true })vapre('deleteMany') - Xato hook —
(err, doc, next); tashqi amallar — middleware da emas
Keyingi dars: 47-dars — Statics va Methods — model va instance metodlari