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

MongoDB native driver: Node'dan MongoClient, find, kursor va update operatorlari

Qisqacha: Rasmiy mongodb paketi (kanonda 7.7.0) — Node'dan MongoDB bilan ishlashning asosiy yo'li; Mongoose ham ichida shuni ishlatadi. Ilova bitta MongoClient ochadi va uni hamma so'rovlar uchun qayta ishlatadi: kanonda har so'rovga yangi client 15.16 ms, bitta client 2.07 ms. Filtr — obyekt ({ teglar: "ish", ustuvorlik: { $in: [...] } }), o'zgartirish — operatorlar bilan ($set, $inc, $push, $addToSet, $pull). Operatorsiz replaceOne butun hujjatni almashtirib yuboradi.

Bu darsda

  • MongoClient bilan ulanasiz va nega uni bitta qilib saqlash kerakligini o'lchov bilan ko'rasiz.
  • Postgres'dagi ma'lumotni insertMany bilan Mongo'ga ko'chirasiz.
  • find filtrlari, projection, saralash va kursor bilan ishlaysiz.
  • updateOne operatorlarini, natija obyektini va upsert'ni o'qiysiz.
  • replaceOne tuzog'i, bulkWrite va directConnection xatosini tushunasiz.

Oldin bilishingiz kerak: MongoDB tuzilishi va vositalari, node-postgres (pg) asoslari, Connection pooling, Ommaviy ma'lumot: COPY, batch va cursor.

1. Nega bu kerak?

Oldingi darsda mongosh'da hujjatlarni ko'rdik. Lekin ilova terminal orqali ishlamaydi — u Node kodidan so'rov yuboradi. Postgres uchun bu ishni pg paketi qilardi (node-postgres asoslari). Mongo uchun — rasmiy drayver (driver): mongodb npm paketi.

Nega darhol Mongoose emas? Ikki sabab:

  1. Mongoose — drayver ustidagi qavat. Uning xatti-harakatini (ayniqsa xatolarni) tushunish uchun pastdagi qavatni bilish kerak.
  2. Ko'p loyihalar Mongoose'siz yozilgan: skriptlar, migratsiyalar, serverless funksiyalar, TypeScript'li yangi loyihalar.

O'xshatish: avtomatik uzatmali mashinani haydash uchun mexanikani bilish shart emas. Lekin yo'lda to'xtab qolsa — kapot ostida nima borligini bilgan odam muammoni tez topadi.

Bu darsning hamma chiqishlari — kanon tajribasidan: Node 24.21.0, mongodb 7.7.0, MongoDB 8.3.13 (Docker, bitta tugunli rs0), Windows 11. ObjectId'lar va vaqtlar sizda boshqacha bo'ladi.

2. Ulanish: MongoClient

2.1 Birinchi ulanish

O'rnatish (o'z loyihangizda):

bash
npm i --save-exact mongodb@7.7.0

Kanon yordamchi faylidagi ulanish:

js
import { MongoClient } from "mongodb";

const MONGO_URL = "mongodb://127.0.0.1:27018/vazifalar"
  + "?replicaSet=rs0&directConnection=true";

const client = new MongoClient(MONGO_URL);
await client.connect();
const db = client.db("vazifalar");
const vazifalar = db.collection("vazifalar");

console.log(await vazifalar.countDocuments()); // 48
await client.close();

Uch qavat — oldingi darsdagidek: client (server bilan aloqa), client.db(…) (baza), db.collection(…) (kolleksiya). db() va collection() tarmoqqa bormaydi — ular faqat "manzil" obyektlari. So'rov countDocuments() chaqirilganda ketadi.

client.close() — dastur oxirida. Skriptda unutsangiz, Node jarayoni tugamay osilib turadi: ochiq ulanishlar event loop'ni ushlab turadi (pg'dagi pool.end() bilan bir xil holat).

2.2 Bitta client — butun ilova uchun

MongoClient ichida ulanishlar hovuzi (pool) bor — pg.Pool ning o'xshashi. Kanon sukut sozlamani o'qidi:

text
pool sozlamasi sukut: maxPoolSize 100 | minPoolSize 0

100 tagacha ulanish, kerak bo'lganda ochiladi. Shuning uchun client'ni ilova boshida bir marta yaratib, hamma joyda qayta ishlatish kerak. Har so'rovda yangi client — har safar TCP ulanish, server bilan tanishuv (handshake) va topologiyani aniqlash.

Kanon o'lchadi: bitta findOne — 30 marta, mediana.

js
const yangi = await olcha(30, async () => {
  const k = new MongoClient(MONGO_URL);
  await k.connect();
  await k.db("vazifalar").collection("vazifalar")
    .findOne({ pgId: 1 });
  await k.close();
});
const qayta = await olcha(30, async () => {
  await db.collection("vazifalar").findOne({ pgId: 1 });
});

olcha(n, fn) — kanon yordamchisi: funksiyani n marta bajarib, vaqtlarning medianasini qaytaradi. Natija:

text
har safar yangi client: 15.16 ms; bitta client (pool): 2.07 ms
findOne: har safar yangi client va bitta client
  • Har so'rovga yangi MongoClient15,16 ms
  • Bitta client (pool)2,07 ms

Manba: IlmHamroh kanon o'lchovi, 2026-10-25: Node 24.21.0, mongodb 7.7.0, MongoDB 8.3.13 (Docker, WSL2), i5-12500H; 30 marta, mediana

7 martaga yaqin farq — va bu baza shu kompyuterda bo'lgandagi holat. Bulutdagi bazada TLS tanishuvi qo'shiladi, farq yanada oshadi (hisob, sinalmagan). Express ilovasida client'ni modul darajasida yarating va har so'rovda db.collection(…) ni qayta ishlating.

Tekshirib ko'ring: Ilovada 3 ta modul (vazifalar.js, izohlar.js, foydalanuvchilar.js) bor. Har biri o'z new MongoClient(...) ini ochsa, nima bo'ladi?

Javob

Uchta alohida hovuz paydo bo'ladi — har biri 100 tagacha ulanish. Server tomonda ulanishlar soni uch barobar o'sadi, xotira ko'proq ketadi. To'g'risi — bitta modulda bitta client yaratib, uni eksport qilish; qolgan modullar shu client'dan db() va collection() oladi.

3. Yozish: Postgres'dan ko'chirish

3.1 insertMany

Kanon Postgres holat 11 dagi foydalanuvchilarni o'qib, har biriga yangi ObjectId berdi. Postgres id → ObjectId moslikni Map da saqladi — vazifaning egasi maydoni uchun kerak (qatorlar telefon uchun bo'lingan):

js
import { ObjectId } from "mongodb";

const { rows: f } = await pg.query(`SELECT id, email, ism,
  sozlamalar, yaratilgan_vaqt FROM foydalanuvchilar ORDER BY id`);
const fIds = new Map();
const fDocs = f.map((r) => {
  const _id = new ObjectId();
  fIds.set(Number(r.id), _id);
  return {
    _id, pgId: Number(r.id), email: r.email, ism: r.ism,
    sozlamalar: r.sozlamalar, yaratilgan: r.yaratilgan_vaqt,
  };
});
const natija = await db.collection("foydalanuvchilar")
  .insertMany(fDocs);
console.log("insertMany foydalanuvchilar:", natija.insertedCount);

new ObjectId() — id'ni bazaga so'ramasdan, drayverning o'zida yaratish (ObjectId tuzilishi). Shuning uchun vazifalarni yozishdan oldin har foydalanuvchining _id si ma'lum.

Vazifalar uchun SQL tomonda teglar array_agg, izohlar json_agg bilan yig'ildi (oldingi darsning 3-mashqi shu ishni JavaScript'da qilgan edi). Keyin har qatordan hujjat:

js
const vDocs = v.map((r) => ({
  pgId: Number(r.id),
  egasi: fIds.get(Number(r.egasi_id)),
  matn: r.matn,
  bajarildi: r.bajarildi,
  ustuvorlik: r.ustuvorlik,
  ...(r.muddat ? { muddat: r.muddat } : {}),
  yaratilgan: r.yaratilgan_vaqt,
  tartib: r.tartib,
  versiya: r.versiya,
  ...(r.ochirilgan_vaqt ? { ochirilgan: r.ochirilgan_vaqt } : {}),
  teglar: r.teglar,
  izohlar: r.izohlar.map((i) => ({
    muallif: i.muallif === null ? null : fIds.get(i.muallif),
    matn: i.matn,
    yaratilgan: new Date(i.yaratilgan),
  })),
}));

Kanon kodida bu qismda yana bir-ikki maydon bor (bajarilgan), mohiyati bir xil. Bitta nozik joy: ...(r.muddat ? { muddat } : {}). Postgres'da muddatsiz vazifada muddat ustuni NULL edi. Mongo'da kanon bunday vazifaga muddat maydonini umuman qo'ymadi. Sxemasiz bazada "maydon yo'q" va "maydon null" — ikki xil holat. Bu farq filtrda muhim, quyida $exists da ko'ramiz.

izohlar ichidagi yaratilgan uchun new Date(...) — json_agg sanani satrga aylantirgan edi. Usiz Mongo'ga satr yozilardi va sana bo'yicha saralash buzilardi.

text
insertMany foydalanuvchilar: 8
insertMany vazifalar: 48

Maslahat: insertMany sukut bo'yicha tartibli (ordered: true): birinchi xatoda to'xtaydi, oldingilari yozilgan qoladi. { ordered: false } — xatoli hujjatni o'tkazib, qolganlarini yozadi. Katta importda (kanon 500 000 vazifani 50 000 lik bo'laklarda ko'chirdi) ko'pincha ordered: false tanlanadi.

4. O'qish: find, filtr va projection

4.1 Filtr — obyekt

SQL'da shart — matn (WHERE egasi_id = 1 AND …). Mongo'da shart — obyekt. Kalit — maydon nomi, qiymat — teng bo'lishi kerak bo'lgan qiymat yoki operator obyekti.

Azizning Savatda bo'lmagan, o'qish tegli vazifalari, faqat to'rt maydon, tartib bo'yicha:

js
const proj = { _id: 0, pgId: 1, matn: 1, teglar: 1, tartib: 1 };
await db.collection("vazifalar").find(
  {
    egasi: aziz,
    ochirilgan: { $exists: false },
    teglar: "o'qish",
  },
  { projection: proj },
).sort({ tartib: 1 }).toArray();

Natijada uchta narsaga qarang: nechta hujjat keldi, qaysi maydonlar bor va tartib qanday ketma-ketlikda:

text
[
  {
    pgId: 1,
    matn: "JavaScript kursining 26-qismini boshlash #o'qish",
    tartib: 1,
    teglar: [ "o'qish" ]
  },
  {
    pgId: 2,
    matn: "Postgres'ni Docker'da ko'tarish #o'qish #docker",
    tartib: 2,
    teglar: [ 'docker', "o'qish" ]
  },
  {
    pgId: 34,
    matn: "SQL JOIN mashqlarini yechish #o'qish",
    tartib: 11,
    teglar: [ "o'qish" ]
  }
]

Har qatorni o'qiymiz:

  • egasi: aziz — tenglik. aziz — Azizning ObjectId'si (fIds.get(1)).
  • ochirilgan: { $exists: false } — "bu maydon yo'q". Kanon Savatdagi vazifaga ochirilgan sanasini qo'ygan, qolganlariga — umuman qo'ymagan. Postgres'dagi ochirilgan_vaqt IS NULL ning o'xshashi.
  • teglar: "o'qish" — massivda shu element bor (oldingi darsdagi mongosh misoli kabi).
  • Kalitlar orasida — VA. Bir obyektdagi hamma shart bajarilishi kerak.
  • { projection: proj } — qaysi maydonlar qaytsin. SQL'dagi SELECT pgId, matn, ….
  • .sort({ tartib: 1 }) — 1 o'sish, -1 kamayish tartibida.

4.2 Asosiy filtr operatorlari

Operator Ma'nosi SQL o'xshashi
$eq, $ne teng, teng emas =, <>
$gt, $gte, $lt, $lte katta, kichik >, >=, <, <=
$in, $nin ro'yxatda bor / yo'q IN, NOT IN
$exists maydon bor / yo'q IS NOT NULL ga yaqin
$regex naqshga mos ~, ILIKE
$or, $and, $not mantiqiy OR, AND, NOT
$elemMatch massivdagi bitta element hamma shartga mos EXISTS (…)
$size massiv uzunligi cardinality(…) = n

$in va $regex birga. Ustuvorligi o'rta yoki past, matni "bozor" bilan boshlanadigan (katta-kichik harf farqsiz — i):

js
await db.collection("vazifalar").find(
  {
    ustuvorlik: { $in: ["orta", "past"] },
    matn: { $regex: "^bozor", $options: "i" },
  },
  { projection: proj },
).toArray();
text
$in + $regex (i): [
  {
    pgId: 8,
    matn: 'Bozordan kartoshka va piyoz olish #bozor',
    tartib: 3,
    teglar: [ 'bozor' ]
  },
  {
    pgId: 16,
    matn: 'Bozordan meva olish #bozor',
    tartib: 3,
    teglar: [ 'bozor' ]
  },
  {
    pgId: 48,
    matn: 'Bozordan kartoshka va piyoz olish #bozor',
    tartib: 13,
    teglar: [ 'bozor' ]
  }
]

^ — satr boshi (langarlar). Bu yerda .sort yo'q, shuning uchun tartib — bazadagi joylashuv tartibi; unga tayanmang.

4.3 Massivlar: $elemMatch va $size

js
await db.collection("vazifalar").countDocuments(
  { izohlar: { $elemMatch: { muallif: aziz } } });   // 3
await db.collection("vazifalar").countDocuments(
  { teglar: { $size: 2 } });                         // 7
text
$elemMatch izohlar: 3
$size teglar = 2: 7

$elemMatch — "izohlar massivida bitta element bor, va u hamma shartga mos". Bitta shartda { "izohlar.muallif": aziz } ham ishlaydi. Ikki shart bo'lsa farq chiqadi: { "izohlar.muallif": aziz, "izohlar.matn": "Tez" } — shartlar turli elementlarda bajarilishi mumkin. $elemMatch esa ikkalasini bir elementda talab qiladi.

$size faqat aniq son bilan ishlaydi ($size: 2); "2 dan ko'p" deb bo'lmaydi. Bunday savol uchun aggregation kerak (Aggregation pipeline).

Tekshirib ko'ring: { muddat: null } filtri muddatsiz vazifalarni topadimi? Kanon muddatsiz vazifaga muddat maydonini umuman yozmagan.

Javob

Topadi: Mongo'da { maydon: null } ham null qiymatli, ham maydoni yo'q hujjatlarga mos keladi. Faqat "maydon yo'q" kerak bo'lsa — { muddat: { $exists: false } }; faqat "qiymati aynan null" kerak bo'lsa — { muddat: { $type: "null" } }. Postgres'da bu farq yo'q edi: ustun doim bor, faqat NULL bo'lishi mumkin. Bu MongoDB hujjatidagi qoida; kanon aynan shu filtrni sinamadi.

5. Kursor: hammasini xotiraga yuklamasdan

find() darhol natija qaytarmaydi. U kursor (cursor) qaytaradi — natijani bo'lak-bo'lak olib keladigan obyekt. toArray() hamma bo'laklarni yig'ib bitta massiv qiladi: 48 hujjat uchun yaxshi, 5 million uchun — xotira tugaydi.

Katta natijada for await bilan aylanamiz (async iteratorlar, pg-cursor bilan bir xil g'oya):

js
const cur = db.collection("vazifalar")
  .find({}, { projection: { _id: 0, pgId: 1 } })
  .sort({ pgId: 1 })
  .batchSize(10);
let n = 0;
for await (const _ of cur) n++;
console.log("cursor bilan o'qildi:", n);
text
cursor bilan o'qildi: 48

batchSize(10) — server har safar 10 tadan hujjat yuboradi. Kursor 48 hujjat uchun 5 marta murojaat qiladi, lekin xotirada bir vaqtda ko'pi bilan 10 ta turadi. for await tugaganda yoki break qilinganda kursor serverda o'zi yopiladi.

6. O'zgartirish: update operatorlari

6.1 updateOne va natija obyekti

SQL'da UPDATE … SET. Mongo'da updateOne(filtr, o'zgarish). O'zgarish — operatorlar obyekti:

js
const u1 = await db.collection("vazifalar").updateOne(
  { pgId: 2 },
  {
    $set: {
      bajarildi: true,
      bajarilgan: new Date("2026-10-24T07:00:00Z"),
    },
    $inc: { versiya: 1 },
    $addToSet: { teglar: "docker" },
  },
);
console.log("updateOne:", JSON.stringify(u1));
text
updateOne: {"acknowledged":true,"modifiedCount":1,"upsertedId":null,"upsertedCount":0,"matchedCount":1}
Operator Nima qiladi
$set maydonga qiymat qo'yadi (yo'q bo'lsa — qo'shadi)
$unset maydonni o'chiradi
$inc songa qo'shadi (-1 — ayiradi)
$push massiv oxiriga qo'shadi
$addToSet massivda yo'q bo'lsa qo'shadi
$pull massivdan mos elementlarni olib tashlaydi

Natija obyekti: matchedCount — filtrga mos kelganlar, modifiedCount — haqiqatan o'zgarganlar. Ular farq qilishi mumkin:

js
const r = await db.collection("vazifalar").updateOne(
  { pgId: 2 }, { $addToSet: { teglar: "docker" } });
console.log("$addToSet takror — modifiedCount:", r.modifiedCount);
text
$addToSet takror — modifiedCount: 0

docker allaqachon bor — hujjat topildi, lekin o'zgarmadi. Postgres'dagi UPDATE … RETURNING dan farqli, bu yerda yangi hujjat qaytmaydi. Yangilangan hujjatning o'zi kerak bo'lsa — findOneAndUpdate (Mongoose versiyasini keyingi darslarda ko'ramiz).

Keyin kanon izoh qo'shdi ($push), tegni olib tashladi ($pull) va natijani projection ichidagi $size bilan o'qidi:

js
await V.updateOne({ pgId: 2 }, { $push: { izohlar: {
  muallif: aziz, matn: "Mongo'da izoh",
  yaratilgan: new Date("2026-10-24T07:05:00Z"),
} } });
await V.updateOne({ pgId: 2 }, { $pull: { teglar: "docker" } });
await V.findOne({ pgId: 2 }, { projection: {
  _id: 0, teglar: 1, versiya: 1,
  izohlarSoni: { $size: "$izohlar" },
} });

V — db.collection("vazifalar") ning qisqa nomi.

text
{ versiya: 2, teglar: [ "o'qish" ], izohlarSoni: 2 }

Hammasi bitta hujjat ichida va har biri atomik: ikki foydalanuvchi bir vaqtda $inc qilsa, ikkala qo'shish ham saqlanadi. JavaScript'da "o'qi → oshir → yoz" qilsangiz, biri yo'qolardi (lost update).

6.2 Topilmasa — xato emas; upsert

js
await V.updateOne({ pgId: 999 }, { $set: { x: 1 } });
await V.updateOne(
  { pgId: 999 }, { $set: { matn: "upsert" } }, { upsert: true });
text
topilmagan filtr — xato emas: {"acknowledged":true,"modifiedCount":0,"upsertedId":null,"upsertedCount":0,"matchedCount":0}
upsert: {"acknowledged":true,"modifiedCount":0,"upsertedId":"6add9a83d285c3ac62248dec","upsertedCount":1,"matchedCount":0}

Birinchisi: hech narsa topilmadi, xato ham yo'q. API'da buni tekshirmasangiz, mavjud bo'lmagan vazifani "yangiladim" deb 200 qaytarib yuborasiz — matchedCount === 0 bo'lsa 404 bering (Status kodlarni to'g'ri tanlash).

Ikkinchisi — upsert (update + insert): topilmasa, filtr va $set dan yangi hujjat yaratadi. upsertedId — yangi hujjatning ObjectId'si. Postgres'dagi INSERT … ON CONFLICT ning o'xshashi (UPSERT va MERGE).

6.3 Ko'pchilikni o'zgartirish va o'chirish

  • updateMany(filtr, o'zgarish) — mos kelgan hammasini.
  • deleteOne(filtr), deleteMany(filtr) — o'chirish; natijada deletedCount.
  • deleteMany({}) — kolleksiyadagi hamma hujjatni o'chiradi. Postgres'dagi DELETE ni WHERE siz yozish bilan bir xil xavf (UPDATE va DELETE xavfsiz).

7. Tuzoq: operatorsiz o'zgartirish

7.1 replaceOne butun hujjatni almashtiradi

Kanon ataylab xato qildi — 3-vazifani "bajarildi" qilmoqchi bo'lib, replaceOne ga operatorsiz obyekt berdi:

js
await V.replaceOne({ pgId: 3 }, { bajarildi: true });
await V.findOne(
  { bajarildi: true, matn: { $exists: false } },
  { projection: { _id: 0 } },
);
text
tuzoq — operatorsiz replaceOne butun hujjatni almashtiradi:
{ bajarildi: true }

Hujjatdan faqat _id va bajarildi qoldi. Matn, egasi, teglar, izohlar, hatto pgId — hammasi yo'qoldi. Xato ham, ogohlantirish ham yo'q. Postgres'da bunday bo'lmaydi: UPDATE … SET bajarildi = true faqat bitta ustunga tegadi.

Bu "yarim hujjat" kanonda keyinroq haqiqiy xato berdi: aggregation'dagi $size maydoni yo'q hujjatda yiqildi (Aggregation pipeline darsida xato matni bilan). Sxemasizlikning narxi — bitta qatorlik xato boshqa darsdagi hisobotni buzdi.

7.2 updateOne operatorsiz — xato

Drayver updateOne da shu xatodan himoya qiladi:

js
try {
  await V.updateOne({ pgId: 4 }, { bajarildi: true });
} catch (x) {
  console.log("updateOne operatorsiz →", x.constructor.name,
    x.message);
}
text
updateOne operatorsiz → MongoInvalidArgumentError Update document requires atomic operators

Tarjima: "O'zgarish hujjati atomik operatorlarni talab qiladi". Ya'ni ikkinchi argumentdagi kalitlar $ bilan boshlanishi kerak. Xato serverdan emas — drayverning o'zi so'rovni yubormasdan rad etdi (MongoInvalidArgumentError — drayver sinfi).

Qoida: o'zgartirish — har doim operator bilan. replaceOne ni faqat butun hujjatni ataylab almashtirmoqchi bo'lganingizda ishlating. (Mongoose bu yerda boshqacha yo'l tutadi: operatorsiz obyektni o'zi $set ga o'raydi — Mongoose CRUD darsida.)

Tekshirib ko'ring: updateOne({ pgId: 5 }, { $set: { teglar: ["ish"] } }) va updateOne({ pgId: 5 }, { $addToSet: { teglar: "ish" } }) — farqi nima?

Javob

$set butun massivni almashtiradi: oldingi teglar yo'qoladi, faqat ["ish"] qoladi. $addToSet esa mavjud massivga ish ni qo'shadi (yo'q bo'lsa) — boshqa teglar saqlanadi. Bitta tegni qo'shish uchun $addToSet yoki $push, butun ro'yxatni almashtirish uchun $set.

8. bulkWrite: bir so'rovda aralash amallar

Ko'p o'zgarishni bittadan yuborish — har biriga tarmoq aylanishi (N+1 muammosi bilan bir xil narx). bulkWrite turli amallarni bitta so'rovda yuboradi:

js
const b = await V.bulkWrite([
  { updateMany: {
    filter: { teglar: "bozor" },
    update: { $set: { ustuvorlik: "past" } },
  } },
  { insertOne: { document: {
    matn: "bulk vazifa", egasi: aziz, teglar: [], izohlar: [],
  } } },
  { deleteOne: { filter: { matn: "bulk vazifa" } } },
], { ordered: true });
console.log({ matched: b.matchedCount, modified: b.modifiedCount,
  inserted: b.insertedCount, deleted: b.deletedCount });
text
{ matched: 4, modified: 4, inserted: 1, deleted: 1 }

ordered: true — amallar tartib bilan bajariladi (shuning uchun uchinchisi ikkinchisi qo'shgan hujjatni o'chira oldi), xatoda to'xtaydi. Muhim: bulkWrite — tranzaksiya emas. Ikkinchi amal yiqilsa, birinchisining natijasi qoladi. "Hammasi yoki hech biri" kerak bo'lsa — session va tranzaksiya (Replica set, tranzaksiyalar va change streams).

9. directConnection: kanon topilmasi

Kanon ulanish satrida directConnection=true bor edi. Usiz nima bo'lishini sinadi:

js
const k = new MongoClient(
  "mongodb://127.0.0.1:27018/vazifalar?replicaSet=rs0",
  { serverSelectionTimeoutMS: 3000 },
);
await k.connect();
await k.db("vazifalar").collection("vazifalar").findOne({});
text
MongoServerSelectionError (3012 ms): connect ECONNREFUSED 127.0.0.1:27017

Diqqat qiling: biz 27018 ga ulandik, xato esa 27017 haqida. Nega?

sequenceDiagram
  participant D as Drayver
  participant M as Mongo :27018
  D->>M: hello (sen kimsan?)
  M-->>D: rs0 a'zolari: 127.0.0.1:27017
  D->>D: topologiyani yangilash
  D->>M: 127.0.0.1:27017 ga ulanish
  Note over D: kompyuterda 27017 yopiq
  D-->>D: 3 s kutib ECONNREFUSED

Diagrammaga qarang. Replica set rejimida drayver birinchi serverdan "to'plamda kimlar bor?" deb so'raydi. Server o'zini konteyner ichidagi nomi bilan e'lon qiladi: 127.0.0.1:27017 (replica set shu manzil bilan yaratilgan — oldingi darsdagi rs.initiate). Drayver shu manzilga ketadi. Kompyuterda esa 27017 yopiq — port 27018 ga yo'naltirilgan.

directConnection=true — "topologiyani qidirma, faqat shu serverga ulan". Docker'dagi bitta tugunli sinov bazasi uchun to'g'ri yechim. Haqiqiy uch tugunli replica set'da esa directConnection qo'yilmaydi: drayver primary almashganini aynan topologiya orqali biladi.

serverSelectionTimeoutMS: 3000 — kanon kutishni 3 soniya qilib qo'ydi. Sukut 30 soniya: ilova yarim daqiqa "o'ylab", keyin yiqiladi.

10. TypeScript bilan

Drayver TypeScript turlari bilan keladi. Kolleksiyaga hujjat turini berasiz — find natijasi va filtr kalitlari tekshiriladi (kanon TS'da sinamadi, bu hujjat bo'yicha):

ts
import { MongoClient, type ObjectId } from "mongodb";

interface VazifaHujjati {
  _id?: ObjectId;
  pgId: number;
  matn: string;
  bajarildi: boolean;
  teglar: string[];
}

const client = new MongoClient(process.env.MONGO_URL!);
const v = client.db("vazifalar")
  .collection<VazifaHujjati>("vazifalar");
const bitta = await v.findOne({ pgId: 1 });
// bitta: WithId<VazifaHujjati> | null

Bu turlar faqat kompilyatsiya paytida ishlaydi. Bazada nima borligini ular kafolatlamaydi: matn: 42 li hujjat kelsa, TypeScript bilmaydi. Ish vaqtidagi tekshiruv — Mongoose yoki Zod bilan.

11. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Postgres'da qoladi, commit yo'q. Kanon bu darsda qilgan ish (kanon o'lchovi, IlmHamroh, 2026-10):

  1. Holat 11 namunasini Mongo'ga ko'chirdi: 8 foydalanuvchi, 48 vazifa (teglar va izohlar ichida, egasi — ObjectId ishora).
  2. find + $exists, $in, $regex, $elemMatch, $size, projection, sort, kursor.
  3. updateOne operatorlari, upsert, replaceOne tuzog'i, bulkWrite.
  4. Ulanish narxi: 15.16 ms vs 2.07 ms; directConnection xatosi.

Bu ko'chirilgan baza qismning qolgan Mongo darslarida ishlatiladi: aggregation, indekslar, xavfsizlik.

12. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
Har so'rovda new MongoClient 7 marta sekin, ulanishlar ko'payadi Ilovada bitta client
replaceOne(f, { maydon: x }) Hujjatning qolgani jim o'chadi updateOne(f, { $set: { maydon: x } })
matchedCount ni tekshirmaslik Yo'q hujjat "yangilandi" deb 200 matchedCount === 0 → 404
Katta natijada toArray() Xotira tugaydi for await + batchSize
Sanani satr qilib yozish Saralash va diapazon noto'g'ri new Date(…)
client.close() unutilgan skript Jarayon tugamay osilib turadi finally { await client.close() }
Docker'dagi replica set'ga directConnection siz ECONNREFUSED 127.0.0.1:27017 ?directConnection=true

13. Mashqlar

Mashqlar kurs/mashqlar/27/43-native-driver/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik, bazasiz — Mongo qoidalarining kichik modeli).

1-mashq (oson): to'ldiring

  • Filtrga mos kelgan hujjatlar soni natija obyektining maydonida
  • Massivda yo'q bo'lsagina qo'shadigan operator —
  • Topilmasa yangi hujjat yaratish sozlamasi — { : true }
  • Maydon yo'qligini tekshiradigan filtr: { ochirilgan: { $exists: } }
  • MongoClient hovuzining sukut maxPoolSize qiymati —
Yechim
  • matchedCount — mos kelganlar; haqiqatan o'zgarganlar — modifiedCount.
  • $addToSet — takrorni qo'shmaydi (modifiedCount: 0).
  • upsert — update + insert.
  • false — $exists: false — maydon yo'q hujjatlar.
  • 100 — kanon client.options.maxPoolSize dan o'qigan.

2-mashq (o'rta): filtr qanday ishlaydi

Mongo filtrining kichik modelini yozing: mos(hujjat, filtr) — true/false. Qo'llab-quvvatlasin: oddiy tenglik (maydon massiv bo'lsa — biror element teng bo'lsa yetadi), $in, $exists, $gte. Kalitlar orasida — VA. Uchta vazifa bilan sinang: Azizning o'qish tegli vazifalari va tartib >= 5 bo'lganlar.

Ishora: Array.isArray, Object.entries, every — massiv metodlari.

Yechim
js
function shartMos(qiymat, shart) {
  const isOp = shart !== null && typeof shart === "object"
    && !Array.isArray(shart)
    && Object.keys(shart).every((k) => k.startsWith("$"));
  if (!isOp) {
    // tenglik: massivda — biror element teng bo'lsa yetadi
    return Array.isArray(qiymat)
      ? qiymat.includes(shart) : qiymat === shart;
  }
  return Object.entries(shart).every(([op, arg]) => {
    if (op === "$exists") return (qiymat !== undefined) === arg;
    if (op === "$in") {
      return arg.some((a) => shartMos(qiymat, a));
    }
    if (op === "$gte") return qiymat >= arg;
    throw new Error(`qo'llanmagan operator: ${op}`);
  });
}

function mos(hujjat, filtr) {
  return Object.entries(filtr)
    .every(([maydon, shart]) => shartMos(hujjat[maydon], shart));
}

const vazifalar = [
  { pgId: 1, egasi: "aziz", teglar: ["o'qish"], tartib: 1 },
  { pgId: 2, egasi: "aziz", teglar: ["docker", "o'qish"],
    tartib: 2, ochirilgan: "2026-10-20" },
  { pgId: 34, egasi: "aziz", teglar: ["o'qish"], tartib: 11 },
];
const f1 = {
  egasi: "aziz",
  teglar: "o'qish",
  ochirilgan: { $exists: false },
};
console.log(vazifalar.filter((v) => mos(v, f1)).map((v) => v.pgId));
const f2 = { tartib: { $gte: 5 } };
console.log(vazifalar.filter((v) => mos(v, f2)).map((v) => v.pgId));
const f3 = { teglar: { $in: ["docker", "bozor"] } };
console.log(vazifalar.filter((v) => mos(v, f3)).map((v) => v.pgId));

Konsolda:

text
[ 1, 34 ]
[ 34 ]
[ 2 ]

Nimaga qarang: teglar: "o'qish" massivda includes bilan tekshirildi — Mongo'dagi "massivga tenglik element bo'yicha" qoidasi. $in ham massiv maydonda ishlaydi: biror element ro'yxatda bo'lsa yetadi. 2-vazifa birinchi filtrdan ochirilgan maydoni borligi uchun chiqib ketdi. Haqiqiy Mongo qoidalari bundan ancha boy (turlar tartibi, ichki yo'llar, null), lekin g'oya shu.

3-mashq (qiyin): update operatorlarini qo'llash

yangila(hujjat, ozgarish) funksiyasini yozing. U yangi hujjat va modified (o'zgardimi) qaytarsin, asl hujjatga tegmasin. Operatorlar: $set, $inc, $push, $addToSet, $pull. Ikkinchi argumentda $ siz kalit bo'lsa — drayverdagidek Update document requires atomic operators xatosini tashlasin.

Ishora: nusxa uchun structuredClone (chuqur nusxa); "o'zgardimi" — oldingi va keyingi holatni JSON.stringify bilan solishtiring.

Yechim
js
function yangila(hujjat, ozgarish) {
  const kalitlar = Object.keys(ozgarish);
  if (!kalitlar.length || !kalitlar.every((k) => k.startsWith("$"))) {
    throw new Error("Update document requires atomic operators");
  }
  const h = structuredClone(hujjat);
  for (const [op, maydonlar] of Object.entries(ozgarish)) {
    for (const [m, q] of Object.entries(maydonlar)) {
      if (op === "$set") h[m] = q;
      else if (op === "$inc") h[m] = (h[m] ?? 0) + q;
      else if (op === "$push") (h[m] ??= []).push(q);
      else if (op === "$addToSet") {
        h[m] ??= [];
        if (!h[m].includes(q)) h[m].push(q);
      } else if (op === "$pull") {
        h[m] = (h[m] ?? []).filter((x) => x !== q);
      } else throw new Error(`qo'llanmagan operator: ${op}`);
    }
  }
  const modified = JSON.stringify(h) !== JSON.stringify(hujjat);
  return { hujjat: h, modified };
}

const v = { pgId: 2, versiya: 1, teglar: ["docker", "o'qish"] };
const a = yangila(v, {
  $set: { bajarildi: true }, $inc: { versiya: 1 },
  $addToSet: { teglar: "docker" },
});
console.log(a.modified, JSON.stringify(a.hujjat));
const b = yangila(a.hujjat, { $addToSet: { teglar: "docker" } });
console.log("takror $addToSet:", b.modified);
const c = yangila(b.hujjat, { $pull: { teglar: "docker" } });
console.log(JSON.stringify(c.hujjat.teglar), "| asl:", v.versiya);
try {
  yangila(v, { bajarildi: true });
} catch (x) {
  console.log(x.message);
}

Konsolda:

text
true {"pgId":2,"versiya":2,"teglar":["docker","o'qish"],"bajarildi":true}
takror $addToSet: false
["o'qish"] | asl: 1
Update document requires atomic operators

Kanon natijalari bilan bir xil mantiq: takror $addToSet — o'zgarish yo'q (modifiedCount: 0), operatorsiz obyekt — rad. structuredClone asl hujjatni himoya qiladi (asl: 1). Haqiqiy Mongo'da esa hammasi server ichida va atomik bajariladi — bu model faqat qoidalarni ko'rsatadi.

14. Real ishda

  • Drayver to'g'ridan — skriptlar, migratsiyalar, ma'lumot ko'chirish, serverless funksiyalar, Mongoose'ni ortiqcha deb bilgan TypeScript loyihalari.
  • Bitta client qoidasi serverless'da ayniqsa muhim: client'ni funksiyadan tashqarida yaratib, chaqiruvlar orasida qayta ishlatish.
  • Intervyu savollari: "updateOne va replaceOne farqi?", "matchedCount va modifiedCount?", "nega update atomik?", "kursor nima uchun kerak?".
  • Docker'dagi replica set va directConnection muammosi — jamoalarda tez-tez uchraydigan "lokalda ulanmayapti" savolining sababi.

Xulosa

  • mongodb drayveri (kanon 7.7.0): MongoClient → db() → collection(); client'da hovuz bor, uni butun ilovada bitta qiling (15.16 vs 2.07 ms).
  • Filtr — obyekt: tenglik, $in, $exists, $regex, $elemMatch, $size; kalitlar orasida VA; massivga tenglik — element bo'yicha.
  • find kursor qaytaradi: katta natijada for await + batchSize.
  • O'zgartirish — operatorlar bilan ($set, $inc, $push, $addToSet, $pull), atomik; matchedCount/modifiedCount ni o'qing; upsert — topilmasa yaratish.
  • Operatorsiz replaceOne hujjatni jim almashtiradi; updateOne operatorsiz — MongoInvalidArgumentError.
  • Docker'dagi bitta tugunli replica set'ga — directConnection=true.

Keyingi dars: Mongoose: schema va model — sxemasiz bazaga ilova darajasida tartib: turlar, majburiy maydonlar, enum, validatorlar va kutilmagan kolleksiya nomlari.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
MongoDB native driver: Node'dan MongoClient, find, kursor va update operatorlari — IlmHamroh