Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Middleware (hooks)
- 2.1 pre("save") — Mongoose 9 uslubi
- 2.2 Document va query middleware
- 2.3 post hook
- 3. Virtual, metodlar va statiklar
- 3.1 Virtual maydon
- 3.2 Hujjat metodlari va statiklar
- 4. Discriminator: bitta kolleksiyada turli shakllar
- 4.1 Muammo
- 4.2 Plugin: qayta ishlatiladigan qo'shimcha
- 5. lean(): tezlik va narxi
- 6. __v va versiya kaliti
- 7. TypeScript: schema'dan tur
- 7.1 InferSchemaType va HydratedDocument
- 7.2 Tuzoq: Boolean jim any bo'lib qoladi
- 8. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): middleware zanjiri
- 3-mashq (qiyin): discriminator modeli
- 11. Real ishda
- Xulosa
- Manbalar
Mongoose ilg'or: middleware, virtual, metodlar, discriminator, lean va TypeScript
Qisqacha: Mongoose schema'ga xulq ham qo'shiladi. Middleware — saqlashdan oldin/keyin ishlaydigan funksiya (Mongoose 9 da
next()yo'q, oddiyasyncfunksiya). Virtual — bazada saqlanmaydigan hisoblangan maydon. methods/statics — hujjat va model metodlari. Discriminator — bitta kolleksiyada turli shakllar (__tmaydoni bilan). Plugin — qayta ishlatiladigan qo'shimcha.lean()oddiy obyekt qaytaradi — kanonda 10.57 ms o'rniga 6.13 ms. TypeScript turlariInferSchemaTypebilan schema'dan chiqariladi — lekin@types/nodeulanmasa,Booleanmaydonlar jimanybo'lib qoladi.
Bu darsda
pre/postmiddleware yozasiz va document hamda query middleware farqini bilasiz.- Virtual maydon, hujjat metodi va model statik metodini qo'shasiz.
- Discriminator bilan bitta kolleksiyada ikki xil vazifa turini saqlaysiz.
- Plugin yozasiz va
lean()ni qachon ishlatishni o'lchov bilan tanlaysiz. - Schema'dan TypeScript turini chiqarasiz va
anytuzog'ini chetlab o'tasiz.
Oldin bilishingiz kerak: Mongoose CRUD va update operatorlari, Populate va embed vs reference, Triggerlar, TypeScript generiklari.
1. Nega bu kerak?
Mongoose CRUD darsida bir muammoni ko'rdik: vazifa teglari matndan ajratiladi, lekin bu mantiq qayerda yashaydi? Agar har API handler'ida teglar = teglariniOl(matn) yozilsa — bittasida unutiladi va ma'lumot buziladi.
Postgres'da bunday qoidani bazaga qo'ygan edik: trigger matn o'zgarganda vazifa_teglari ni yangiladi (Triggerlar). Mongo'da trigger yo'q (Atlas'dagi triggerlar bundan mustasno). Mongoose esa schema'ning o'ziga xulq biriktirishga imkon beradi: "saqlashdan oldin shuni qil", "bu maydonni hisobla", "bu hujjat shunday metodga ega".
O'xshatish: ish hujjati blankasi (oldingi darsdagi o'xshatish) endi yo'riqnoma bilan keladi: "topshirishdan oldin sanani qo'ying", "jami summani pastga yozing". Blanka nafaqat shaklni, balki tartibni ham belgilaydi.
Chiqishlar uch manbadan: kanon o'lchovi (IlmHamroh, 2026-10; mongoose 9.11.2, MongoDB 8.3.13), bazasiz sinovlarimiz ("biz ishga tushirdik": mongoose 9.11.2, Node 24.21.0, ulanishsiz) va TypeScript tekshiruvimiz (typescript 6.0.3).
2. Middleware (hooks)
2.1 pre("save") — Mongoose 9 uslubi
Middleware (yoki hook) — Mongoose ma'lum amal oldidan (pre) yoki keyin (post) chaqiradigan funksiya. Kanon schema'sidagi middleware — teglarni matndan ajratadi:
const TEG =
/(?<![\p{L}\p{N}_#])#[\p{L}\p{N}_]+(?:['‘’][\p{L}\p{N}_]+)*/gu;
VazifaS.pre("save", async function () {
if (this.isModified("matn")) {
const topilgan = this.matn.match(TEG) ?? [];
this.teglar = [...new Set(
topilgan.map((t) => t.slice(1).toLowerCase()),
)];
}
});(Regex — vazifalar-api dagi teg qoidasining o'zi (lookbehind, Unicode regex); kanon kodi bitta qatorda, bu yerda bo'lingan.)
Uch muhim nuqta:
this— hujjatning o'zi. Shuning uchun strelkali funksiya emas,functionyoziladi (this qoidasi): strelkali funksiyadathishujjat bo'lmaydi.this.isModified("matn")— matn o'zgarganmi? Faqatbajarildio'zgarsa, teglarni qayta hisoblash kerak emas.next()yo'q. Eski Mongoose kodidapre("save", function (next) { …; next(); })ni ko'p uchratasiz. Mongoose 9 danextargumenti olib tashlangan — middlewareasyncfunksiya (yoki Promise qaytaradigan), tugashi — davom etish signali,throw— saqlashni to'xtatish. Kanon buni ham qayd qildi.
Natija kanonda ko'rindi: "Non olish #uy #Bozor" → teglar: ["uy", "bozor"] (Mongoose schema darsidagi strict misolida) — #Bozor kichik harfga o'tdi.
Diqqat: Eski darslardan ko'chirilgan
function (next) { … next(); }Mongoose 9 da ishlamaydi. Biz bazasiz sinadik (mongoose 9.11.2):pre("validate", function (next) { … })ichidatypeof next—undefined, ya'ninext()chaqiruviTypeErrorbilan yiqiladi. Migratsiya qilayotganda hammaprehook'larni qidirib,nextni olib tashlang (Mongoose 9 migratsiya qo'llanmasida batafsil).
2.2 Document va query middleware
Oldingi darsda findByIdAndUpdate dan keyin teglar eskirib qolgan edi: pre("save") ishlamadi. Sababi — middleware ikki xil:
| Tur | Qachon | this |
|---|---|---|
| document | save, validate, deleteOne (hujjatda) |
hujjat |
| query | find, findOne, updateOne, findOneAndUpdate, deleteMany … |
so'rov (Query) obyekti |
| aggregate | aggregate |
aggregation obyekti |
| model | insertMany |
model |
save hook'i faqat save da. Update orqali o'zgarishga ham qoida kerak bo'lsa — query middleware (hujjat bo'yicha; kanon bu hook'ni sinamadi):
VazifaS.pre("findOneAndUpdate", async function () {
// { $set: { matn } } yoki { matn }
const oz = this.getUpdate();
const matn = oz.$set?.matn ?? oz.matn;
if (matn !== undefined) {
this.setUpdate({ ...oz, $set: { ...oz.$set,
teglar: teglariniOl(matn) } });
}
});Bu yerda this hujjat emas — so'rov. Hujjatning boshqa maydonlarini bilmaysiz, faqat o'zgarishni ko'rasiz. Shuning uchun qoidani bitta funksiyaga (teglariniOl) chiqarib, ikkala hook'dan chaqirish yaxshi.
2.3 post hook
post — amal tugagandan keyin. Natija yoki xato bilan ishlaydi: log yozish, keshni tozalash, 11000 xatosini chiroyli xatoga aylantirish (hujjat bo'yicha):
FoydalanuvchiS.post("save", function (xato, hujjat, next) {
if (xato?.code === 11000) next(new Error("Bu email band"));
else next(xato);
});Xato ishlovchi post hook — Mongoose 9 da ham next oladigan istisno shakl (uch argumentli). Buni ham bazasiz sinadik: post("validate", function (xato, hujjat, next)) da next — funksiya, next(new Error("chiroyli xato")) asl ValidationError o'rniga shu xatoni chiqardi. Oddiy post("save", async function (hujjat) { … }) — next siz.
Tekshirib ko'ring:
pre("save")da email'ni kichik harfga o'tkazdingiz. Admin panel foydalanuvchiniupdateOne({ _id }, { $set: { email: "ALI@Example.com" } })bilan o'zgartirdi. Bazada nima bo'ladi?
Javob
ALI@Example.com — o'zgarishsiz. updateOne — query amal, save hook'i chaqirilmaydi. Yechimlar: schema'da lowercase: true (bu cast paytida, update'da ham qo'llanadi — u middleware emas), yoki pre("updateOne") query hook'i. Oddiy normallashtirish uchun schema sozlamasi ishonchliroq.
3. Virtual, metodlar va statiklar
3.1 Virtual maydon
Virtual — bazada saqlanmaydigan, o'qishda hisoblanadigan maydon. Kanon izohlar sonini virtual qildi:
VazifaS.virtual("izohlarSoni").get(function () {
return this.izohlar.length;
});
VazifaS.set("toJSON", { virtuals: true, versionKey: false });virtual izohlarSoni (toJSON): trueKanon JSON.stringify(hujjat) ichida "izohlarSoni":1 borligini tekshirdi — bor. toJSON: { virtuals: true } siz virtual JSON'ga tushmaydi: Express res.json(vazifa) qilsa, frontend izohlarSoni ni ko'rmaydi. versionKey: false — __v ni JSON'dan yashirish.
Virtual va hisoblangan maydon farqi: virtual har o'qishda hisoblanadi va bazada yo'q — u bo'yicha filtr yoki saralash qilib bo'lmaydi. "Eng ko'p izohli vazifalar" kerak bo'lsa — izohlar sonini haqiqiy maydonda saqlang ($inc bilan; computed naqshi).
Virtual lean() bilan ham ishlamaydi — lean oddiy obyekt qaytaradi (pastda).
3.2 Hujjat metodlari va statiklar
VazifaS.methods.bajar = function () {
this.bajarildi = true;
this.versiya += 1;
return this.save();
};
VazifaS.statics.faollari = function (egasi) {
return this.find({ egasi, bajarildi: false })
.sort({ yaratilgan: 1 });
};methods— har hujjatda:vazifa.bajar().this— hujjat.statics— modelda:Vazifa.faollari(azizId).this— model.
method bajar(): 2 | static faollari: 2bajar() — versiya 1 dan 2 ga, faollari — Azizning 2 ta bajarilmagan vazifasi. Foyda: biznes amallari bir joyda, nomli va qayta ishlatiladigan. Lekin bajar() ichida save() — o'qi-o'zgartir-saqla. Ikki so'rov bir vaqtda kelsa, versiya += 1 biri yo'qolishi mumkin (lost update); atomik variant — updateOne({ _id }, { $set: { bajarildi: true }, $inc: { versiya: 1 } }).
4. Discriminator: bitta kolleksiyada turli shakllar
4.1 Muammo
vazifalar ga takroriy vazifa kerak: "har kuni", "har hafta". Oddiy vazifada bu maydon yo'q. Ikkita kolleksiya? Unda "hamma vazifalarim" ro'yxati ikki so'rov bo'ladi. Bitta kolleksiya, ixtiyoriy har maydoni? Unda oddiy vazifada har: "kun" ham validatsiyadan o'tadi.
Discriminator — Mongoose'ning meros (inheritance) mexanizmi: asosiy model + kengaytirilgan schema, hammasi bitta kolleksiyada. Mongoose har hujjatga tur belgisi — __t maydonini yozadi.
const Takroriy = Vazifa.discriminator("Takroriy", new Schema({
har: { type: String, enum: ["kun", "hafta"], required: true },
}));
await Takroriy.create({
egasi: aziz._id, matn: "Namoz #din", har: "kun",
});{ matn: 'Namoz #din', __t: 'Takroriy', har: 'kun' } | Vazifa.countDocuments: 205 | Takroriy: 1Kanon chiqishini o'qiymiz:
- Hujjat
vazifaskolleksiyasida,__t: 'Takroriy'bilan. Vazifa.countDocuments()— 205: oddiy vazifalar va takroriylar birga (asosiy model hammasini ko'radi).Takroriy.countDocuments()— 1: discriminator modeli filtrga o'zi{ __t: "Takroriy" }qo'shadi.Takroriyhujjatida asosiy schema qoidalari ham ishlaydi (matn,egasi,pre("save")— teglar), ustigaharqoidalari.
Postgres'da bunga o'xshash naqsh — bitta jadval + tur ustuni + CHECK (single table inheritance, Modellash naqshlari).
4.2 Plugin: qayta ishlatiladigan qo'shimcha
Plugin — schema'ni oladigan funksiya: maydon, metod yoki hook qo'shadi. Ko'p modelda bir xil xulq kerak bo'lganda (soft delete, yaratuvchi, audit) — har schema'da takrorlamay, bitta plugin. Kanon plugin'ni sinamadi — biz bazasiz sinadik (plugin + pre("validate") + virtual + discriminator birga):
function ochirishPlagini(schema) {
schema.add({ ochirilgan: { type: Date, default: null } });
schema.methods.savatga = function () {
this.ochirilgan = new Date("2026-10-25T09:00:00Z");
};
}
const vazifaSchema = new Schema({
matn: { type: String, required: true },
teglar: [String],
});
vazifaSchema.plugin(ochirishPlagini);
vazifaSchema.pre("validate", async function () {
this.teglar = (this.matn.match(/#\p{L}+/gu) ?? [])
.map((t) => t.slice(1).toLowerCase());
});
vazifaSchema.virtual("qisqa").get(function () {
return this.matn.slice(0, 10);
});
const Vazifa = model("Vazifa", vazifaSchema);
const Takroriy = Vazifa.discriminator(
"Takroriy",
new Schema({ har: { type: String, enum: ["kun", "hafta"] } }),
);
const v = new Takroriy({
matn: "Bozorga borish #Bozor #uy", har: "kun",
});
await v.validate();
v.savatga();
const { _id, id, ...qolgan } = v.toObject({ virtuals: true });
console.log(qolgan);Natija (biz ishga tushirdik: mongoose 9.11.2, Node 24.21.0, ulanishsiz):
{
matn: 'Bozorga borish #Bozor #uy',
teglar: [ 'bozor', 'uy' ],
ochirilgan: 2026-10-25T09:00:00.000Z,
__t: 'Takroriy',
har: 'kun',
qisqa: 'Bozorga bo'
}Hammasi bitta hujjatda ishladi: plugin qo'shgan ochirilgan maydoni va savatga() metodi, async pre("validate") (next siz) hisoblagan teglar, discriminator belgisi __t, virtual qisqa. _id va id (virtual id) — tasodifiy, shuning uchun chiqishdan olib tashladik.
pre("validate") ni pre("save") o'rniga tanladik: validate() ni bazasiz chaqirish mumkin, save() uchun esa ulanish kerak. Saqlashda tartib: avval validate hook'lari, keyin validatsiya, keyin save hook'lari:
flowchart LR
A["save()"] --> B["pre validate hook'lari"]
B --> C["validatsiya: required, enum"]
C --> D["pre save hook'lari"]
D --> E["bazaga yozish"]
E --> F["post save hook'lari"]
B -.->|throw| X["to'xtadi, xato"]
C -.->|ValidationError| X
D -.->|throw| XDiagrammaga qarang: istalgan bosqichda xato chiqsa, undan keyingilar bajarilmaydi va bazaga hech narsa yozilmaydi. 2-mashqda shu zanjirning modelini yozasiz.
5. lean(): tezlik va narxi
Mongoose find natijasidagi har hujjat — hydrated (to'liq) hujjat: getter/setter, save(), isModified, virtual'lar, o'zgarish kuzatuvi. Bu qulay, lekin har hujjat uchun ish. lean() esa bazadan kelgan oddiy JS obyektini qaytaradi.
Kanon 200 hujjatni ikki usulda o'qidi (15 marta, mediana):
const tHyd = await olcha(15, () => Vazifa.find({ matn: /^Ommaviy/ }));
const tLean = await olcha(15, () =>
Vazifa.find({ matn: /^Ommaviy/ }).lean());
const h = await Vazifa.findOne({ matn: /^Ommaviy/ });
const l = await Vazifa.findOne({ matn: /^Ommaviy/ }).lean();hydrated 10.57 ms vs lean 6.13 ms; hydrated — model, save() bor: function; lean — Object, save: undefined- Hydrated (Mongoose hujjati)10,57 ms
- lean() (oddiy obyekt)6,13 ms
Manba: IlmHamroh kanon o'lchovi, 2026-10-25: Node 24.21.0, mongoose 9.11.2, MongoDB 8.3.13 (Docker, WSL2), i5-12500H; 15 marta, mediana
lean() 40% ga yaqin tez. Lekin chiqishning ikkinchi qismiga qarang: hydrated hujjat — model sinfidan (h.constructor.name), save bor; lean — oddiy Object, save: undefined. Lean bilan nima yo'qoladi:
save(),isModified()va boshqa hujjat metodlari,methodsdagi o'z metodlaringiz.- virtual'lar (
izohlarSoniyo'q). - getter'lar va schema'dagi tur o'zgartirishlari o'qishda.
Qoida: faqat o'qib, JSON qilib yuboradigan joyda (API ro'yxati) — lean(). Hujjatni o'zgartirib saqlash kerak bo'lsa — hydrated.
6. __v va versiya kaliti
Mongoose schema darsida hujjatda __v: 0 paydo bo'lgan edi. Bu versionKey: Mongoose massivlarni save() qilganda xavfli o'zgarishlarni (massiv elementini indeks bo'yicha almashtirish) shu raqam bilan tekshiradi. Ikki jarayon bitta hujjatning massivini bir vaqtda o'zgartirsa, ikkinchisi VersionError oladi (hujjat bo'yicha).
To'liq optimistik qulf uchun schema sozlamasi optimisticConcurrency: true bor — har save() da __v tekshiriladi va oshiriladi (Optimistic concurrency darsidagi versiya ustuni g'oyasi). Kanon schema'sida u false edi va o'zining versiya maydonini ishlatdi; VersionError holatlari kanonda sinalmadi.
7. TypeScript: schema'dan tur
7.1 InferSchemaType va HydratedDocument
Turlarni ikki marta yozmaslik uchun (bir — schema, bir — interface) Mongoose ularni schema'dan chiqaradi. Kanon bu qismni TypeScript bilan sinamadi — biz tsc bilan tekshirdik:
import mongoose, {
Schema, model, type InferSchemaType, type HydratedDocument,
} from "mongoose";
const vazifaSchema = new Schema({
matn: { type: String, required: true },
bajarildi: { type: Boolean, default: false },
teglar: { type: [String], default: [] },
izoh: String,
}, { timestamps: true });
type Vazifa = InferSchemaType<typeof vazifaSchema>;
type VazifaHujjati = HydratedDocument<Vazifa>;
const VazifaModel = model("Vazifa", vazifaSchema);
const v: VazifaHujjati = new VazifaModel({ matn: "Non olish" });
const matn: string = v.matn;
const izoh: string | null | undefined = v.izoh;
const vaqt: Date = v.createdAt;
v.bajarildi = "ha";
console.log(matn, izoh, vaqt, mongoose.version);InferSchemaType— oddiy ma'lumot turi:requiredmaydon — majburiy (matn: string), default'li — majburiy (bajarildi: boolean), qolgani — ixtiyoriy vanullham bo'lishi mumkin (izoh?: string | null | undefined).timestamps: true—createdAt,updatedAt.HydratedDocument<T>— shu ma'lumot + Mongoose hujjat metodlari (save,isModified…).
v.bajarildi = "ha" — ataylab xato. tsc (typescript 6.0.3, mongoose 9.11.2, --strict --module nodenext --target es2024 --types node) natijasi:
c.ts(20,1): error TS2322: Type 'string' is not assignable to type 'boolean'.Kutilganidek: bajarildi — boolean, satr qo'yib bo'lmaydi.
7.2 Tuzoq: Boolean jim any bo'lib qoladi
Endi xuddi shu faylni --types node siz, lekin --skipLibCheck bilan tekshirdik. Natija — xato yo'q, exit 0. v.bajarildi = "ha" o'tib ketdi.
Nega? Turni ko'rish uchun ataylab xato qildik — tur boshqa narsaga tayinlandi, va tsc xabarida chiqarilgan tur ko'rindi:
e.ts(4,7): error TS2741: Property 'a' is missing in type '{ matn: string; bajarildi: any; b3: any; teglar: string[]; b2?: any; izoh?: string | null | undefined; } & DefaultTimestampProps' but required in type '{ a: 1; }'.bajarildi: any — hamma Boolean maydonlar any. --types node bilan esa xuddi shu xabar bajarildi: boolean; b3: boolean; … b2?: boolean | null | undefined ni ko'rsatdi. Mongoose'ning Boolean turi Node turlariga (@types/node) bog'liq; ular ulanmasa, tur jim any ga tushadi, skipLibCheck esa bu haqdagi xatolarni yashiradi. TypeScript 6 da types sukut bo'yicha bo'sh — skipLibCheck siz ishga tushirganimizda tsc aynan shuni aytdi: Cannot find name 'dns'. Do you need to install type definitions for node? Try npm i --save-dev @types/node and then add 'node' to the types field in your tsconfig.
Amaliy qoida: Mongoose'li TypeScript loyihasida tsconfig.json da "types": ["node"] bo'lsin va @types/node o'rnatilgan bo'lsin (tsconfig qoidalari). Turlar "ishlayapti" deb ishonishdan oldin bitta ataylab xato bilan tekshiring.
Tekshirib ko'ring:
InferSchemaTypedaizoh?: string | null | undefinedchiqdi. Neganullham bor, faqatundefinedemas?
Javob
MongoDB'da maydon uch holatda bo'lishi mumkin: yo'q (undefined), null qiymatli yoki satr. Schema'da required yo'q — Mongoose null ni ham qabul qiladi va bazada null bo'lgan hujjat ham bo'lishi mumkin. Native driver darsidagi "maydon yo'q va maydon null — ikki xil holat" g'oyasining tur tizimidagi aksi.
8. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
vazifalar-api Postgres'da: teglar trigger bilan, versiya — versiya ustuni va optimistik qulf, turlar — o'z interface lari. Kanon Mongoose'da sinagani:
pre("save")—async,next()siz (Mongoose 9).- Virtual
izohlarSoni+toJSON: { virtuals: true }, methodbajar(), staticfaollari(). - Discriminator
Takroriy(__t), umumiy son 205 va 1. lean()6.13 ms vs hydrated 10.57 ms.
Biz qo'shganlar: plugin va pre("validate") bazasiz sinovi; InferSchemaType/HydratedDocument ni tsc bilan tekshiruv va types: node siz Boolean → any tuzog'i.
9. Ko'p uchraydigan xatolar
| Xato | Oqibat | To'g'risi |
|---|---|---|
Mongoose 9 da pre(…, function (next)) |
Hook buziladi | async function () { … }, xatoda throw |
| Hook'da strelkali funksiya | this hujjat emas |
function |
Update'da save hook'ini kutish |
Hisoblangan maydon eskiradi | Query middleware yoki save() |
toJSON da virtuals siz |
API javobida virtual yo'q | set("toJSON", { virtuals: true }) |
lean() natijasida doc.save() |
TypeError: doc.save is not a function |
Saqlash kerak bo'lsa — hydrated |
| Virtual bo'yicha filtr/saralash | Ishlamaydi — bazada yo'q | Haqiqiy maydon + $inc |
types: ["node"] siz Mongoose turlari |
Boolean → any, xatolar yashirinadi |
@types/node + "types": ["node"] |
10. Mashqlar
Mashqlar kurs/mashqlar/27/47-mongoose-ilgor/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik — Mongoose mexanizmlarining kichik modeli, kutubxonasiz).
1-mashq (oson): to'ldiring
- Mongoose 9 da
prehook'ida yo'q bo'lgan argument — - Bazada saqlanmaydigan hisoblangan maydon —
- Discriminator tur belgisini qaysi maydonga yozadi?
- Oddiy obyekt qaytaradigan, tezroq o'qish usuli —
- Hujjat metodi
schema.methods, model metodi —schema.
Yechim
- next — Mongoose 9:
asyncfunksiya; xato —throw. - virtual —
toJSON: { virtuals: true }siz JSON'ga tushmaydi. - __t — asosiy model hammasini, discriminator faqat o'z turini ko'radi.
- lean — kanonda 6.13 vs 10.57 ms;
saveyo'q. - statics —
this— model.
2-mashq (o'rta): middleware zanjiri
saqla(hujjat, hooklar) yozing — Mongoose saqlash tartibining modeli: avval hooklar.validate dagi hamma pre funksiyalar (ketma-ket, await bilan), keyin hooklar.save dagilar, keyin "bazaga yozish" (console.log("saqlandi:", …)). Hook this sifatida hujjatni olsin. Birorta hook xato tashlasa — keyingilar ham, yozish ham bajarilmasin, xato yuqoriga chiqsin.
Ishora: await fn.call(hujjat) — call va apply.
Yechim
async function saqla(hujjat, hooklar) {
for (const bosqich of ["validate", "save"]) {
for (const fn of hooklar[bosqich] ?? []) {
await fn.call(hujjat);
}
}
console.log("saqlandi:", JSON.stringify(hujjat));
}
const hooklar = {
validate: [
async function () {
this.teglar = (this.matn.match(/#\p{L}+/gu) ?? [])
.map((t) => t.slice(1).toLowerCase());
},
],
save: [
async function () {
if (this.matn.length > 100) throw new Error("matn uzun");
},
async function () {
this.yangilangan = "2026-10-25T09:00:00.000Z";
},
],
};
await saqla({ matn: "Non olish #Uy" }, hooklar);
try {
await saqla({ matn: "x".repeat(101) + " #ish" }, hooklar);
} catch (x) {
console.log("to'xtadi:", x.message);
}Konsolda:
saqlandi: {"matn":"Non olish #Uy","teglar":["uy"],"yangilangan":"2026-10-25T09:00:00.000Z"}
to'xtadi: matn uzunIkkinchi hujjatda validate hook'i ishladi (teglar hisoblandi), birinchi save hook'i xato tashladi — yangilangan qo'yilmadi va "saqlandi" chiqmadi. Mongoose 9 dagi next siz hook'lar aynan shunday: tugash — davom, throw — to'xtash.
3-mashq (qiyin): discriminator modeli
Xotiradagi bitta "kolleksiya" uchun modelYasa(kol, tur) yozing. tur berilmasa — asosiy model, berilsa — discriminator. Model metodlari: create(hujjat) (discriminator __t: tur qo'shadi) va countDocuments() (asosiy — hammasini, discriminator — faqat o'z __t sini sanaydi). Kanon sonlarini qayta hosil qiling: 204 oddiy vazifa + 1 takroriy → 205 va 1.
Ishora: Array.from({ length: 204 }, …) — Array.from.
Yechim
function modelYasa(kol, tur) {
const filtr = (h) => (tur ? h.__t === tur : true);
return {
create(hujjat) {
const h = tur ? { ...hujjat, __t: tur } : { ...hujjat };
kol.push(h);
return h;
},
countDocuments() {
return kol.filter(filtr).length;
},
};
}
const kol = [];
const Vazifa = modelYasa(kol);
const Takroriy = modelYasa(kol, "Takroriy");
Array.from({ length: 204 },
(_, i) => Vazifa.create({ matn: `v${i}` }));
const h = Takroriy.create({ matn: "Namoz #din", har: "kun" });
console.log(h, "| Vazifa.countDocuments:", Vazifa.countDocuments(),
"| Takroriy:", Takroriy.countDocuments());Konsolda:
{ matn: 'Namoz #din', har: 'kun', __t: 'Takroriy' } | Vazifa.countDocuments: 205 | Takroriy: 1Kanon chiqishi bilan bir xil sonlar. Maydonlar tartibi farq qiladi: kanon hujjatni bazadan o'qigan, u yerda __t saqlangan joyida turadi; bizning modelimiz esa __t ni oxiriga qo'shadi. Mexanizm Mongoose'dagidek: bitta kolleksiya, discriminator modeli har so'rovga { __t: … } filtrini o'zi qo'shadi.
11. Real ishda
- Middleware — slug yasash, parol hash'lash (28-qismda — Parolni xavfsiz saqlash), audit maydonlari, kesh tozalash. Ko'p katta loyihada Mongoose 8 → 9 yangilanishining asosiy ishi —
nextli hook'larni qayta yozish. lean()— API ro'yxatlarida deyarli standart; profiling'da birinchi topiladigan "arzon" tezlashtirish.- Discriminator — bildirishnoma turlari, to'lov turlari, CMS bloklari: umumiy maydonlar va turga xos qo'shimchalar.
- TypeScript — NestJS + Mongoose loyihalarida
InferSchemaType/HydratedDocumentyoki klass asosidagi schema'lar (Mongoose bilan NestJS).
Xulosa
- Middleware:
pre/post; Mongoose 9 danext()yo'q —asyncfunksiya, xatodathrow; document va query hook'lari alohida. - Virtual — saqlanmaydigan hisoblangan maydon;
toJSON: { virtuals: true }bilan JSON'ga; filtr valeanda yo'q. methods— hujjatda,statics— modelda; discriminator — bitta kolleksiya,__tbelgisi (kanon: 205 va 1); plugin — qayta ishlatiladigan qo'shimcha.lean()— kanonda 6.13 vs 10.57 ms, lekinsave, virtual va metodlar yo'q.InferSchemaType+HydratedDocument— schema'dan tur;@types/nodeva"types": ["node"]sizBooleanjimany.
Keyingi dars: Aggregation pipeline — $match, $group, $lookup, $unwind, $facet, $setWindowFields va 500 000 hujjatda Postgres bilan bir xil savol.
Manbalar
- Mongoose 9 hujjati — Middleware: https://mongoosejs.com/docs/middleware.html
- Mongoose hujjati — Migrating to 9 (
next()olib tashlangani): https://mongoosejs.com/docs/migrating_to_9.html - Mongoose hujjati — Virtuals, Discriminators, Plugins, lean: https://mongoosejs.com/docs/tutorials/virtuals.html , https://mongoosejs.com/docs/discriminators.html , https://mongoosejs.com/docs/plugins.html , https://mongoosejs.com/docs/tutorials/lean.html
- Mongoose hujjati — TypeScript Schemas (
InferSchemaType): https://mongoosejs.com/docs/typescript/schemas.html - Kanon o'lchovi (IlmHamroh, 2026-10): Node 24.21.0, mongoose 9.11.2, MongoDB 8.3.13 (Docker). Plugin sinovi, TypeScript tekshiruvi va mashq yechimlari — biz ishga tushirdik (mongoose 9.11.2, typescript 6.0.3, Node 24.21.0, Windows 11; 2026-10-25).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!