Node.js: MongoDB مع Node.js
آخر تحديث: 2026-08-26
1. لماذا تختار MongoDB
تواجه واجهة برمجة تطبيقات التجارة الإلكترونية الخاصة بـ «أليس» مشكلة معقدة: تختلف خصائص المنتجات اختلافًا كبيرًا عبر الفئات المختلفة — فالأجهزة المحمولة تحتوي على حقول خاصة بوحدة المعالجة المركزية والذاكرة، والملابس تحتوي على حقول خاصة بالمقاس واللون، والأطعمة تحتوي على حقول خاصة بتاريخ انتهاء الصلاحية. لو تم استخدام قاعدة بيانات علائقية، لكان لا بد من تعديل بنية الجدول في كل مرة تُضاف فيها فئة جديدة. أما نموذج المستندات في MongoDB فيدعم بشكل طبيعي السجلات ذات البنى المختلفة؛ حيث يمكن أن يحتوي كل مستند منتج على حقول مختلفة تمامًا دون الحاجة إلى أي ترحيل للمخطط.
- MongoDB هي قاعدة بيانات قائمة على المستندات تُخزّن البيانات بتنسيق BSON، وهو تنسيق مشابه لتنسيق JSON لكنه يدعم أنواعًا أكثر من البيانات.
- يتوافق نموذج المستند بشكل طبيعي مع نموذج الكائنات في Node.js، مما يقلل من العبء الإضافي الناجم عن تحويلات ORM
- لا يوجد مخطط ثابت؛ يمكن إضافة الحقول أو إزالتها ديناميكيًا مع تطور الأعمال
- قابلية توسع أفقية قوية، مناسبة لسيناريوهات القراءة والكتابة ذات التزامن العالي
- مجموعة واسعة من عوامل التشغيل الخاصة بالاستعلامات وآليات الفهرسة لتلبية متطلبات البحث المعقدة
| مفاهيم SQL | مفاهيم MongoDB | الوصف |
|---|---|---|
| قاعدة البيانات | قاعدة البيانات | قاعدة البيانات، المصطلحات المتسقة |
| جدول | مجموعة | جدول → مجموعة |
| الصف | المستند | الصف → المستند |
| عمود | حقل | عمود → حقل |
| المفتاح الأساسي | _id (ObjectId) |
مفتاح أساسي تم إنشاؤه تلقائيًا |
| JOIN | $lookup (التجميع) |
أنواع مختلفة من استعلامات «join» |
| المخطط | لا يوجد مخطط إلزامي | قاعدة التحقق الاختيارية |
| الفهرس | الفهرس | آليات فهرسة مماثلة |
2. التركيب والتوصيل
(1) تثبيت برنامج تشغيل MongoDB
استخدم حزمة npm الرسمية mongodb للاتصال بخادم MongoDB.
▶ مثال: تثبيت برنامج تشغيل
npm install mongodb
(2) إنشاء اتصال مع العميل
MongoClient هي نقطة الدخول للاتصال بـ MongoDB؛ وهي تحدد العنوان والخيارات عبر سلسلة اتصال.
▶ مثال: الاتصال الأساسي
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
async function connect() {
try {
await client.connect();
console.log('Connected to MongoDB');
const db = client.db('myapp');
return db;
} catch (err) {
console.error('Connection failed:', err.message);
process.exit(1);
}
}
(3) مجموعات الاتصالات وخيارات التكوين
MongoClient تجمع اتصالات مدمج؛ يتم التحكم في حجم التجمع وسلوكه عبر الخيارات.
▶ مثال: الاتصال باستخدام إعدادات تجمع الاتصالات
const client = new MongoClient(uri, {
maxPoolSize: 10,
minPoolSize: 2,
maxIdleTimeMS: 30000,
serverSelectionTimeoutMS: 5000,
connectTimeoutMS: 10000,
});
| خيار التكوين | القيمة الافتراضية | الوصف |
|---|---|---|
maxPoolSize |
100 | الحد الأقصى لعدد الاتصالات في مجموعة الاتصالات |
minPoolSize |
0 | الحد الأدنى لعدد الاتصالات في مجموعة الاتصالات |
maxIdleTimeMS |
0 | مهلة انتظار الاتصال في حالة الخمول (0 = لا توجد مهلة) |
serverSelectionTimeoutMS |
30000 | انتهت مهلة اختيار الخادم |
connectTimeoutMS |
30000 | انتهت مهلة إنشاء الاتصال |
socketTimeoutMS |
0 | انتهت مهلة الاتصال |
retryWrites |
صحيح | إعادة محاولة عمليات الكتابة تلقائيًا |
(4) إغلاق الاتصالات بشكل سلس
عند إنهاء عمل التطبيق، يجب عليه إغلاق الاتصالات وتحرير الموارد.
▶ مثال: الإغلاق السلس
process.on('SIGINT', async () => {
await client.close();
console.log('MongoDB connection closed');
process.exit(0);
});
3. عمليات CRUD
(1) إدراج مستند
استخدم insertOne لإدراج مستند واحد، وinsertMany لإدراج مستندات متعددة.
▶ مثال: إدراج وثائق المنتج
const products = db.collection('products');
const result = await products.insertOne({
name: 'Mechanical Keyboard',
price: 89.99,
category: 'electronics',
specs: { switches: 'Cherry MX Blue', layout: 'ANSI' },
createdAt: new Date(),
});
console.log('Inserted ID:', result.insertedId);
(2) البحث في الوثائق
find يُرجع المؤشر؛ findOne يُرجع مستندًا واحدًا.
▶ مثال: الاستعلام عن المنتجات
const product = await products.findOne({ category: 'electronics' });
console.log(product);
const cursor = products.find({ price: { $gt: 50 } });
const expensive = await cursor.toArray();
console.log(`${expensive.length} products found`);
(3) تحديث الوثائق
updateOne يُحدِّث المطابقة الأولى؛ updateMany يُحدِّث جميع المطابقات.
▶ مثال: تحديث أسعار المنتجات
const updateResult = await products.updateOne(
{ name: 'Mechanical Keyboard' },
{ $set: { price: 79.99, updatedAt: new Date() } },
);
console.log('Modified count:', updateResult.modifiedCount);
(4) حذف مستند
deleteOne يحذف أول نتيجة مطابقة؛ deleteMany يحذف جميع النتائج المطابقة.
▶ مثال: حذف منتج
const deleteResult = await products.deleteOne({
name: 'Mechanical Keyboard',
});
console.log('Deleted count:', deleteResult.deletedCount);
(5) مرجع سريع لطرق CRUD
| العملية | الطريقة | القيمة المرجعة | الوصف |
|---|---|---|---|
| إدراج سجل واحد | insertOne(doc) |
{insertedId} |
إرجاع المعرّف الذي تم إنشاؤه تلقائيًا |
| إدراج متعدد | insertMany([doc]) |
{insertedIds, insertedCount} |
إدراج دفعي |
| الاستعلام عن سجل واحد | findOne(filter) |
مستند أو قيمة فارغة | إرجاع أول نتيجة مطابقة |
| الاستعلام عن عدة صفوف | find(filter) |
المؤشر | يتطلب toArray() أو التكرار |
| تحديث إدخال واحد | updateOne(filter, update) |
{modifiedCount} |
تحديث الإدخال الأول فقط |
| تحديث عدة إدخالات | updateMany(filter, update) |
{modifiedCount} |
تحديث جميع النتائج المطابقة |
| حذف إدخال واحد | deleteOne(filter) |
{deletedCount} |
حذف أول نتيجة مطابقة |
| حذف عدة عناصر | deleteMany(filter) |
{deletedCount} |
حذف جميع النتائج المطابقة |
| استبدال في المستند | replaceOne(filter, doc) |
{modifiedCount} |
استبدال في المستند بأكمله |
4. معرّف الكائن (ObjectId) وعوامل الاستعلام
(1) آلية ObjectId
القيمة الافتراضية _id لكل مستند هي من النوع ObjectId؛ ويتضمن الترميز المكون من 12 بايت طابعًا زمنيًا، ومعرّف الجهاز، وعدادًا.
▶ مثال: استخدام ObjectId
const { ObjectId } = require('mongodb');
const id = new ObjectId();
console.log('ID string:', id.toHexString());
console.log('Timestamp:', id.getTimestamp());
const product = await products.findOne({
_id: new ObjectId('6850a1b2c3d4e5f6a7b8c9d0'),
});
(2) عوامل المقارنة
▶ مثال: استعلام المقارنة
const expensive = await products.find({ price: { $gt: 100 } }).toArray();
const cheap = await products.find({ price: { $lt: 20 } }).toArray();
const midRange = await products.find({ price: { $gte: 50, $lte: 100 } }).toArray();
(3) العوامل المنطقية وعوامل المجموعات
▶ مثال: استعلامات $in و$or
const selected = await products.find({
category: { $in: ['electronics', 'books'] },
}).toArray();
const mixed = await products.find({
$or: [
{ price: { $lt: 10 } },
{ category: 'electronics' },
],
}).toArray();
(4) استعلامات التعبيرات النمطية
▶ مثال: مطابقة أسماء المنتجات باستخدام التعبيرات النمطية
const matched = await products.find({
name: { $regex: /^Mechanical/i },
}).toArray();
(5) مرجع سريع لمشغلات الاستعلام
| المشغل | الصيغة | الوصف |
|---|---|---|
$eq |
{field: {$eq: val}} |
يساوي (و{field: val}) |
$gt |
{field: {$gt: val}} |
أكبر من |
$gte |
{field: {$gte: val}} |
أكبر من أو يساوي |
$lt |
{field: {$lt: val}} |
أقل من |
$lte |
{field: {$lte: val}} |
أقل من أو يساوي |
$ne |
{field: {$ne: val}} |
لا يساوي |
$in |
{field: {$in: [v1,v2]}} |
داخل المصفوفة |
$nin |
{field: {$nin: [v1,v2]}} |
غير موجود في المصفوفة |
$or |
{$or: [{...},{...}]} |
أو الشرط |
$and |
{$and: [{...},{...}]} |
الشروط |
$not |
{field: {$not: {...}}} |
إلغاء التحديد |
$regex |
{field: {$regex: 'pattern'}} |
مطابقة التعبير النمطي |
$exists |
{field: {$exists: true}} |
هل هذا الحقل موجود؟ |
5. الإسقاط والفرز
(1) حقل إرجاع التحكم في العرض
تحدد عملية الاستخراج الحقول التي سيتم إرجاعها أو استبعادها، مما يقلل من حجم حركة مرور الشبكة.
▶ مثال: استعلام الإسقاط
const names = await products.find(
{},
{ projection: { name: 1, price: 1, _id: 0 } },
).toArray();
const withoutSpecs = await products.find(
{},
{ projection: { specs: 0, createdAt: 0 } },
).toArray();
(2) الفرز وترقيم الصفحات
يقوم sort بفرز النتائج، بينما يقوم كل من skip وlimit بتنفيذ تقسيم الصفحات.
▶ مثال: الفرز وتقسيم الصفحات
const page = 2;
const pageSize = 10;
const sorted = await products.find({})
.sort({ price: -1, name: 1 })
.skip((page - 1) * pageSize)
.limit(pageSize)
.toArray();
1تشير إلى الترتيب التصاعدي؛-1تشير إلى الترتيب التنازلي- يتم تطبيق الترتيب حسب الحقول المتعددة وفقًا للترتيب الذي تم تعريفها به
skipيكون الأداء ضعيفًا عند وجود عدد كبير جدًّا من السجلات؛ وبالنسبة لمجموعات البيانات الكبيرة، يُنصح باستخدام استعلامات النطاق بدلاً من ذلك.
6. أساسيات الفهرس
(1) إنشاء فهرس
تعمل الفهارس على تسريع عمليات الاستعلام، لكنها تزيد من عبء الكتابة ومساحة التخزين.
▶ مثال: إنشاء فهرس
await products.createIndex({ name: 1 });
await products.createIndex({ category: 1, price: -1 });
await products.createIndex({ name: 'text' });
const indexes = await products.indexes();
console.log(indexes);
(2) الفهارس الفريدة والفهارس المركبة
▶ مثال: فهرس فريد
await products.createIndex({ sku: 1 }, { unique: true });
- يقوم MongoDB تلقائيًا بإنشاء فهرس فريد لـ
_id - تتبع المؤشرات المركبة مبدأ البادئة الموجودة في أقصى اليسار
- تدعم الفهارس النصية البحث عن النص الكامل؛ ويُسمح بإنشاء فهرس واحد فقط لكل مجموعة
7. عملية الاتصال بـ MongoDB وتشغيله
flowchart TD
A[App Launch] --> B[Create MongoClient]
B --> C[client.connect]
C -->|Success| D[Get db Examples]
C -->|Failure| E[Error Handling/Retry]
E --> C
D --> F[Get collection]
F --> G{CRUD Operation}
G -->|Write| H[insertOne / insertMany]
G -->|Read| I[find / findOne]
G -->|Update| J[updateOne / updateMany]
G -->|Delete| K[deleteOne / deleteMany]
H --> L[Back insertedId]
I --> M[Back to the Document/Cursor]
J --> N[Back modifiedCount]
K --> O[Back deletedCount]
L --> P{Continue?}
M --> P
N --> P
O --> P
P -->|Yes| G
P -->|No| Q[client.close]
Q --> R[Exit the app]
8. مثال شامل: طبقة الوصول إلى بيانات إدارة المنتجات
const { MongoClient, ObjectId } = require('mongodb');
class ProductRepository {
constructor(uri, dbName) {
this.client = new MongoClient(uri, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
});
this.dbName = dbName;
this.collection = null;
}
async connect() {
await this.client.connect();
const db = this.client.db(this.dbName);
this.collection = db.collection('products');
await this.collection.createIndex({ name: 1 });
await this.collection.createIndex({ category: 1, price: -1 });
console.log('ProductRepository connected');
}
async create(productData) {
const doc = {
...productData,
createdAt: new Date(),
updatedAt: new Date(),
};
const result = await this.collection.insertOne(doc);
return { ...doc, _id: result.insertedId };
}
async findById(id) {
return await this.collection.findOne({
_id: new ObjectId(id),
});
}
async findByCategory(category, page = 1, pageSize = 10) {
const skip = (page - 1) * pageSize;
const [items, total] = await Promise.all([
this.collection.find({ category })
.sort({ price: -1 })
.skip(skip)
.limit(pageSize)
.project({ name: 1, price: 1, category: 1 })
.toArray(),
this.collection.countDocuments({ category }),
]);
return { items, total, page, pageSize };
}
async update(id, updates) {
const result = await this.collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: { ...updates, updatedAt: new Date() } },
);
return result.modifiedCount > 0;
}
async delete(id) {
const result = await this.collection.deleteOne({
_id: new ObjectId(id),
});
return result.deletedCount > 0;
}
async disconnect() {
await this.client.close();
console.log('ProductRepository disconnected');
}
}
async function main() {
const repo = new ProductRepository(
'mongodb://localhost:27017',
'ecommerce',
);
try {
await repo.connect();
const created = await repo.create({
name: 'Wireless Mouse',
price: 29.99,
category: 'electronics',
specs: { dpi: 16000, buttons: 6 },
});
console.log('Created:', created._id);
const found = await repo.findById(created._id);
console.log('Found:', found.name);
await repo.update(created._id, { price: 24.99 });
const page = await repo.findByCategory('electronics', 1, 10);
console.log('Page items:', page.items.length);
await repo.delete(created._id);
console.log('Deleted');
} finally {
await repo.disconnect();
}
}
main().catch(console.error);
❓ أسئلة شائعة
id.getTimestamp() دون الحاجة إلى أي حقول إضافية.serverSelectionTimeoutMS لتحديد مدة الانتظار، وسجل الخطأ في كتلة catch، وقم بتنفيذ التدهور التدريجي؛ وفي بيئة الإنتاج، نوصي باستخدام آلية إعادة المحاولة وفحوصات الحالة._id، لا يمكن خلطهما — فإما استخدام الأرقام 1 فقط لتضمين الحقول المحددة، أو استخدام الأرقام 0 فقط لاستبعادها؛ ويتم إرجاع _id افتراضيًّا ويمكن تعيينه على 0 بشكل فردي لاستبعاده.9. التمارين
- اكتب برنامجًا نصيًّا للاتصال بمثيل محلي لـ MongoDB وإدراج 5 مستندات منتجات من فئات مختلفة، بحيث يحتوي كل منها على 3 حقول مختلفة على الأقل.
- تنفيذ استعلام حسب النطاق السعري ($gte/$lte) وفرز النتائج بترتيب تنازلي حسب السعر، مع عرض الحقلين «name» و«price» فقط
- استخدم مزيج
$inو$regexللبحث عن المنتجات التي تندرج فئتها ضمن القائمة المحددة ويحتوي اسمها على كلمة مفتاحية معينة - قم بإنشاء فهارس مركبة للحقول التي يتم الاستعلام عنها بشكل متكرر، واستخدم
explain()لمقارنة الاختلافات في خطط التنفيذ قبل الفهرسة وبعدها. - إنشاء دالة استعلام عامة مقسمة إلى صفحات تقبل المعلمات التالية: filter و projection و sort و page و pageSize
📖 ملخص
- 1 لماذا تختار MongoDB: المفاهيم الأساسية وطرق الاستخدام
- 2 المفاهيم الأساسية وطرق استخدام التركيب والتوصيل
- 3 المفاهيم الأساسية واستخدام عمليات CRUD
- 4 المفاهيم الأساسية واستخدامات ObjectId وعوامل الاستعلام
- 5 مفاهيم أساسية واستخدامات عمليات الإسقاط والفرز
- 6 مفاهيم أساسية وأساليب استخدام أساسيات الفهرسة
- 7 مفاهيم أساسية واستخدامات اتصالات وعمليات MongoDB
- 8 مثال شامل: المفاهيم الأساسية وطرق استخدام طبقة الوصول إلى البيانات في إدارة المنتجات
📝 تمارين
- أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
- قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
- راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
- التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
- حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.