Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ulanish: MongoClient
- 2.1 Birinchi ulanish
- 2.2 Bitta client — butun ilova uchun
- 3. Yozish: Postgres'dan ko'chirish
- 3.1 insertMany
- 4. O'qish: find, filtr va projection
- 4.1 Filtr — obyekt
- 4.2 Asosiy filtr operatorlari
- 4.3 Massivlar: $elemMatch va $size
- 5. Kursor: hammasini xotiraga yuklamasdan
- 6. O'zgartirish: update operatorlari
- 6.1 updateOne va natija obyekti
- 6.2 Topilmasa — xato emas; upsert
- 6.3 Ko'pchilikni o'zgartirish va o'chirish
- 7. Tuzoq: operatorsiz o'zgartirish
- 7.1 replaceOne butun hujjatni almashtiradi
- 7.2 updateOne operatorsiz — xato
- 8. bulkWrite: bir so'rovda aralash amallar
- 9. directConnection: kanon topilmasi
- 10. TypeScript bilan
- 11. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
- 12. Ko'p uchraydigan xatolar
- 13. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): filtr qanday ishlaydi
- 3-mashq (qiyin): update operatorlarini qo'llash
- 14. Real ishda
- Xulosa
- Manbalar
MongoDB native driver: Node'dan MongoClient, find, kursor va update operatorlari
Qisqacha: Rasmiy
mongodbpaketi (kanonda 7.7.0) — Node'dan MongoDB bilan ishlashning asosiy yo'li; Mongoose ham ichida shuni ishlatadi. Ilova bittaMongoClientochadi 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). OperatorsizreplaceOnebutun hujjatni almashtirib yuboradi.
Bu darsda
MongoClientbilan ulanasiz va nega uni bitta qilib saqlash kerakligini o'lchov bilan ko'rasiz.- Postgres'dagi ma'lumotni
insertManybilan Mongo'ga ko'chirasiz. findfiltrlari, projection, saralash va kursor bilan ishlaysiz.updateOneoperatorlarini, natija obyektini va upsert'ni o'qiysiz.replaceOnetuzog'i,bulkWritevadirectConnectionxatosini 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:
- Mongoose — drayver ustidagi qavat. Uning xatti-harakatini (ayniqsa xatolarni) tushunish uchun pastdagi qavatni bilish kerak.
- 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):
npm i --save-exact mongodb@7.7.0Kanon yordamchi faylidagi ulanish:
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:
pool sozlamasi sukut: maxPoolSize 100 | minPoolSize 0100 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.
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:
har safar yangi client: 15.16 ms; bitta client (pool): 2.07 ms- 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'znew 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):
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:
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.
insertMany foydalanuvchilar: 8
insertMany vazifalar: 48Maslahat:
insertManysukut 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'pinchaordered: falsetanlanadi.
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:
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:
[
{
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 vazifagaochirilgansanasini qo'ygan, qolganlariga — umuman qo'ymagan. Postgres'dagiochirilgan_vaqt IS NULLning 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'dagiSELECT pgId, matn, …..sort({ tartib: 1 })—1o'sish,-1kamayish 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):
await db.collection("vazifalar").find(
{
ustuvorlik: { $in: ["orta", "past"] },
matn: { $regex: "^bozor", $options: "i" },
},
{ projection: proj },
).toArray();$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
await db.collection("vazifalar").countDocuments(
{ izohlar: { $elemMatch: { muallif: aziz } } }); // 3
await db.collection("vazifalar").countDocuments(
{ teglar: { $size: 2 } }); // 7$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 vazifagamuddatmaydonini 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):
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);cursor bilan o'qildi: 48batchSize(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:
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));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:
const r = await db.collection("vazifalar").updateOne(
{ pgId: 2 }, { $addToSet: { teglar: "docker" } });
console.log("$addToSet takror — modifiedCount:", r.modifiedCount);$addToSet takror — modifiedCount: 0docker 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:
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.
{ 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
await V.updateOne({ pgId: 999 }, { $set: { x: 1 } });
await V.updateOne(
{ pgId: 999 }, { $set: { matn: "upsert" } }, { upsert: true });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; natijadadeletedCount.deleteMany({})— kolleksiyadagi hamma hujjatni o'chiradi. Postgres'dagiDELETEniWHEREsiz 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:
await V.replaceOne({ pgId: 3 }, { bajarildi: true });
await V.findOne(
{ bajarildi: true, matn: { $exists: false } },
{ projection: { _id: 0 } },
);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:
try {
await V.updateOne({ pgId: 4 }, { bajarildi: true });
} catch (x) {
console.log("updateOne operatorsiz →", x.constructor.name,
x.message);
}updateOne operatorsiz → MongoInvalidArgumentError Update document requires atomic operatorsTarjima: "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"] } })vaupdateOne({ 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:
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 });{ 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:
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({});MongoServerSelectionError (3012 ms): connect ECONNREFUSED 127.0.0.1:27017Diqqat 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 ECONNREFUSEDDiagrammaga 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):
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> | nullBu 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):
- Holat 11 namunasini Mongo'ga ko'chirdi: 8 foydalanuvchi, 48 vazifa (teglar va izohlar ichida, egasi — ObjectId ishora).
find+$exists,$in,$regex,$elemMatch,$size, projection, sort, kursor.updateOneoperatorlari, upsert,replaceOnetuzog'i,bulkWrite.- Ulanish narxi: 15.16 ms vs 2.07 ms;
directConnectionxatosi.
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: } } MongoClienthovuzining sukutmaxPoolSizeqiymati —
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.maxPoolSizedan 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
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:
[ 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
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:
true {"pgId":2,"versiya":2,"teglar":["docker","o'qish"],"bajarildi":true}
takror $addToSet: false
["o'qish"] | asl: 1
Update document requires atomic operatorsKanon 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: "
updateOnevareplaceOnefarqi?", "matchedCountvamodifiedCount?", "nega update atomik?", "kursor nima uchun kerak?". - Docker'dagi replica set va
directConnectionmuammosi — jamoalarda tez-tez uchraydigan "lokalda ulanmayapti" savolining sababi.
Xulosa
mongodbdrayveri (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. findkursor qaytaradi: katta natijadafor await+batchSize.- O'zgartirish — operatorlar bilan (
$set,$inc,$push,$addToSet,$pull), atomik;matchedCount/modifiedCountni o'qing; upsert — topilmasa yaratish. - Operatorsiz
replaceOnehujjatni jim almashtiradi;updateOneoperatorsiz —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
- MongoDB Node.js Driver hujjati (7.x): https://www.mongodb.com/docs/drivers/node/current/
- MongoDB hujjati — Query and Projection Operators: https://www.mongodb.com/docs/manual/reference/operator/query/
- MongoDB hujjati — Update Operators: https://www.mongodb.com/docs/manual/reference/operator/update/
- MongoDB hujjati — Connection String Options (
directConnection,maxPoolSize): https://www.mongodb.com/docs/manual/reference/connection-string-options/ - Kanon o'lchovi (IlmHamroh, 2026-10-25): Node 24.21.0, mongodb 7.7.0, MongoDB 8.3.13 (Docker
mongo:8,rs0), i5-12500H, Windows 11. Mashq yechimlari — biz ishga tushirdik (Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!