IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis47/65-dars17 daqiqa
Mundarija (26)

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, oddiy async funksiya). Virtual — bazada saqlanmaydigan hisoblangan maydon. methods/statics — hujjat va model metodlari. Discriminator — bitta kolleksiyada turli shakllar (__t maydoni bilan). Plugin — qayta ishlatiladigan qo'shimcha. lean() oddiy obyekt qaytaradi — kanonda 10.57 ms o'rniga 6.13 ms. TypeScript turlari InferSchemaType bilan schema'dan chiqariladi — lekin @types/node ulanmasa, Boolean maydonlar jim any bo'lib qoladi.

Bu darsda

  • pre/post middleware 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 any tuzog'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:

js
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:

  1. this — hujjatning o'zi. Shuning uchun strelkali funksiya emas, function yoziladi (this qoidasi): strelkali funksiyada this hujjat bo'lmaydi.
  2. this.isModified("matn") — matn o'zgarganmi? Faqat bajarildi o'zgarsa, teglarni qayta hisoblash kerak emas.
  3. next() yo'q. Eski Mongoose kodida pre("save", function (next) { …; next(); }) ni ko'p uchratasiz. Mongoose 9 da next argumenti olib tashlangan — middleware async funksiya (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) { … }) ichida typeof next — undefined, ya'ni next() chaqiruvi TypeError bilan yiqiladi. Migratsiya qilayotganda hamma pre hook'larni qidirib, next ni 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):

js
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):

js
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 foydalanuvchini updateOne({ _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:

js
VazifaS.virtual("izohlarSoni").get(function () {
  return this.izohlar.length;
});
VazifaS.set("toJSON", { virtuals: true, versionKey: false });
text
virtual izohlarSoni (toJSON): true

Kanon 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

js
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.
text
method bajar(): 2 | static faollari: 2

bajar() — 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.

js
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",
});
text
{ matn: 'Namoz #din', __t: 'Takroriy', har: 'kun' } | Vazifa.countDocuments: 205 | Takroriy: 1

Kanon chiqishini o'qiymiz:

  • Hujjat vazifas kolleksiyasida, __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.
  • Takroriy hujjatida asosiy schema qoidalari ham ishlaydi (matn, egasi, pre("save") — teglar), ustiga har qoidalari.

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):

js
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):

text
{
  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| X

Diagrammaga 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):

js
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();
text
hydrated 10.57 ms vs lean 6.13 ms; hydrated — model, save() bor: function; lean — Object, save: undefined
200 hujjatni o'qish: hydrated va lean()
  • 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, methods dagi o'z metodlaringiz.
  • virtual'lar (izohlarSoni yo'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:

ts
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: required maydon — majburiy (matn: string), default'li — majburiy (bajarildi: boolean), qolgani — ixtiyoriy va null ham 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:

text
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:

text
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: InferSchemaType da izoh?: string | null | undefined chiqdi. Nega null ham bor, faqat undefined emas?

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:

  1. pre("save") — async, next() siz (Mongoose 9).
  2. Virtual izohlarSoni + toJSON: { virtuals: true }, method bajar(), static faollari().
  3. Discriminator Takroriy (__t), umumiy son 205 va 1.
  4. 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 pre hook'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: async funksiya; 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; save yo'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
js
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:

text
saqlandi: {"matn":"Non olish #Uy","teglar":["uy"],"yangilangan":"2026-10-25T09:00:00.000Z"}
to'xtadi: matn uzun

Ikkinchi 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
js
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:

text
{ matn: 'Namoz #din', har: 'kun', __t: 'Takroriy' } | Vazifa.countDocuments: 205 | Takroriy: 1

Kanon 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 — next li 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/HydratedDocument yoki klass asosidagi schema'lar (Mongoose bilan NestJS).

Xulosa

  • Middleware: pre/post; Mongoose 9 da next() yo'q — async funksiya, xatoda throw; document va query hook'lari alohida.
  • Virtual — saqlanmaydigan hisoblangan maydon; toJSON: { virtuals: true } bilan JSON'ga; filtr va lean da yo'q.
  • methods — hujjatda, statics — modelda; discriminator — bitta kolleksiya, __t belgisi (kanon: 205 va 1); plugin — qayta ishlatiladigan qo'shimcha.
  • lean() — kanonda 6.13 vs 10.57 ms, lekin save, virtual va metodlar yo'q.
  • InferSchemaType + HydratedDocument — schema'dan tur; @types/node va "types": ["node"] siz Boolean jim any.

Keyingi dars: Aggregation pipeline — $match, $group, $lookup, $unwind, $facet, $setWindowFields va 500 000 hujjatda Postgres bilan bir xil savol.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Mongoose ilg'or: middleware, virtual, metodlar, discriminator, lean va TypeScript — IlmHamroh