Node.js: MongoDB مع Node.js

آخر تحديث: 2026-08-26

1. لماذا تختار MongoDB

تواجه واجهة برمجة تطبيقات التجارة الإلكترونية الخاصة بـ «أليس» مشكلة معقدة: تختلف خصائص المنتجات اختلافًا كبيرًا عبر الفئات المختلفة — فالأجهزة المحمولة تحتوي على حقول خاصة بوحدة المعالجة المركزية والذاكرة، والملابس تحتوي على حقول خاصة بالمقاس واللون، والأطعمة تحتوي على حقول خاصة بتاريخ انتهاء الصلاحية. لو تم استخدام قاعدة بيانات علائقية، لكان لا بد من تعديل بنية الجدول في كل مرة تُضاف فيها فئة جديدة. أما نموذج المستندات في MongoDB فيدعم بشكل طبيعي السجلات ذات البنى المختلفة؛ حيث يمكن أن يحتوي كل مستند منتج على حقول مختلفة تمامًا دون الحاجة إلى أي ترحيل للمخطط.

مفاهيم SQL مفاهيم MongoDB الوصف
قاعدة البيانات قاعدة البيانات قاعدة البيانات، المصطلحات المتسقة
جدول مجموعة جدول → مجموعة
الصف المستند الصف → المستند
عمود حقل عمود → حقل
المفتاح الأساسي _id (ObjectId) مفتاح أساسي تم إنشاؤه تلقائيًا
JOIN $lookup (التجميع) أنواع مختلفة من استعلامات «join»
المخطط لا يوجد مخطط إلزامي قاعدة التحقق الاختيارية
الفهرس الفهرس آليات فهرسة مماثلة


2. التركيب والتوصيل

(1) تثبيت برنامج تشغيل MongoDB

استخدم حزمة npm الرسمية mongodb للاتصال بخادم MongoDB.

▶ مثال: تثبيت برنامج تشغيل

BASH
npm install mongodb

(2) إنشاء اتصال مع العميل

MongoClient هي نقطة الدخول للاتصال بـ MongoDB؛ وهي تحدد العنوان والخيارات عبر سلسلة اتصال.

▶ مثال: الاتصال الأساسي

JAVASCRIPT
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 تجمع اتصالات مدمج؛ يتم التحكم في حجم التجمع وسلوكه عبر الخيارات.

▶ مثال: الاتصال باستخدام إعدادات تجمع الاتصالات

JAVASCRIPT
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) إغلاق الاتصالات بشكل سلس

عند إنهاء عمل التطبيق، يجب عليه إغلاق الاتصالات وتحرير الموارد.

▶ مثال: الإغلاق السلس

JAVASCRIPT
process.on('SIGINT', async () => {
  await client.close();
  console.log('MongoDB connection closed');
  process.exit(0);
});
▶ جرّب الكود

3. عمليات CRUD

(1) إدراج مستند

استخدم insertOne لإدراج مستند واحد، وinsertMany لإدراج مستندات متعددة.

▶ مثال: إدراج وثائق المنتج

JAVASCRIPT
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 يُرجع مستندًا واحدًا.

▶ مثال: الاستعلام عن المنتجات

JAVASCRIPT
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 يُحدِّث جميع المطابقات.

▶ مثال: تحديث أسعار المنتجات

JAVASCRIPT
const updateResult = await products.updateOne(
  { name: 'Mechanical Keyboard' },
  { $set: { price: 79.99, updatedAt: new Date() } },
);

console.log('Modified count:', updateResult.modifiedCount);
▶ جرّب الكود

(4) حذف مستند

deleteOne يحذف أول نتيجة مطابقة؛ deleteMany يحذف جميع النتائج المطابقة.

▶ مثال: حذف منتج

JAVASCRIPT
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

JAVASCRIPT
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) عوامل المقارنة

▶ مثال: استعلام المقارنة

JAVASCRIPT
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

JAVASCRIPT
const selected = await products.find({
  category: { $in: ['electronics', 'books'] },
}).toArray();

const mixed = await products.find({
  $or: [
    { price: { $lt: 10 } },
    { category: 'electronics' },
  ],
}).toArray();
▶ جرّب الكود

(4) استعلامات التعبيرات النمطية

▶ مثال: مطابقة أسماء المنتجات باستخدام التعبيرات النمطية

JAVASCRIPT
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) حقل إرجاع التحكم في العرض

تحدد عملية الاستخراج الحقول التي سيتم إرجاعها أو استبعادها، مما يقلل من حجم حركة مرور الشبكة.

▶ مثال: استعلام الإسقاط

JAVASCRIPT
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 بتنفيذ تقسيم الصفحات.

▶ مثال: الفرز وتقسيم الصفحات

JAVASCRIPT
const page = 2;
const pageSize = 10;

const sorted = await products.find({})
  .sort({ price: -1, name: 1 })
  .skip((page - 1) * pageSize)
  .limit(pageSize)
  .toArray();
▶ جرّب الكود

6. أساسيات الفهرس

(1) إنشاء فهرس

تعمل الفهارس على تسريع عمليات الاستعلام، لكنها تزيد من عبء الكتابة ومساحة التخزين.

▶ مثال: إنشاء فهرس

JAVASCRIPT
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) الفهارس الفريدة والفهارس المركبة

▶ مثال: فهرس فريد

JAVASCRIPT
await products.createIndex({ sku: 1 }, { unique: true });
▶ جرّب الكود

7. عملية الاتصال بـ MongoDB وتشغيله

100%
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. مثال شامل: طبقة الوصول إلى بيانات إدارة المنتجات

JAVASCRIPT
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);


❓ أسئلة شائعة

س لماذا يُستخدم MongoDB بدلاً من MySQL؟
ج عندما تتغير هياكل البيانات بشكل متكرر ولا تكون الحقول ثابتة، يمكن لنموذج المستندات في MongoDB التكيف دون الحاجة إلى تعديل الجداول؛ أما إذا كان النشاط التجاري ينطوي على عدد كبير من المعاملات وعلاقات معقدة، فإن MySQL يكون أكثر ملاءمة.
س ما هو ObjectId؟
ج ObjectId هو معرّف فريد مكون من 12 بايت يتم إنشاؤه تلقائيًا بواسطة MongoDB. تمثل البايتات الأربعة الأولى طابعًا زمنيًا؛ ويمكنك استرداد وقت الإنشاء باستخدام id.getTimestamp() دون الحاجة إلى أي حقول إضافية.
س كيف ينبغي ضبط حجم تجمع الاتصالات؟
ج بشكل عام، اضبطه على ما يتراوح بين 5 و10 أضعاف عدد نوى وحدة المعالجة المركزية (maxPoolSize)؛ أما بالنسبة للتطبيقات التي تعتمد بشكل مكثف على عمليات الإدخال/الإخراج، فيمكن زيادته حسب الحاجة. اضبط minPoolSize على قيمة تتراوح بين 2 و5 لتجنب تأخيرات بدء التشغيل البارد.
س كيف يمكنني التعامل مع حالات فشل الاتصال؟
ج قم بتعيين المعلمة serverSelectionTimeoutMS لتحديد مدة الانتظار، وسجل الخطأ في كتلة catch، وقم بتنفيذ التدهور التدريجي؛ وفي بيئة الإنتاج، نوصي باستخدام آلية إعادة المحاولة وفحوصات الحالة.
س ما هي الحالات التي يُعدّ MongoDB مناسبًا لها؟
ج إنه مناسب للحالات التي تتطلب مخططات مرنة وعمليات قراءة/كتابة متكررة، مثل إدارة المحتوى، وتحليل السجلات، وبيانات إنترنت الأشياء، وكتالوجات المنتجات؛ وهو غير مناسب للأنظمة المالية الأساسية التي تتطلب اتساقًا قويًّا في المعاملات.
س هل يمكن خلط الأرقام 1 و0 في عملية الإسقاط؟
ج باستثناء _id، لا يمكن خلطهما — فإما استخدام الأرقام 1 فقط لتضمين الحقول المحددة، أو استخدام الأرقام 0 فقط لاستبعادها؛ ويتم إرجاع _id افتراضيًّا ويمكن تعيينه على 0 بشكل فردي لاستبعاده.


9. التمارين

  1. اكتب برنامجًا نصيًّا للاتصال بمثيل محلي لـ MongoDB وإدراج 5 مستندات منتجات من فئات مختلفة، بحيث يحتوي كل منها على 3 حقول مختلفة على الأقل.
  2. تنفيذ استعلام حسب النطاق السعري ($gte/$lte) وفرز النتائج بترتيب تنازلي حسب السعر، مع عرض الحقلين «name» و«price» فقط
  3. استخدم مزيج $in و$regex للبحث عن المنتجات التي تندرج فئتها ضمن القائمة المحددة ويحتوي اسمها على كلمة مفتاحية معينة
  4. قم بإنشاء فهارس مركبة للحقول التي يتم الاستعلام عنها بشكل متكرر، واستخدم explain() لمقارنة الاختلافات في خطط التنفيذ قبل الفهرسة وبعدها.
  5. إنشاء دالة استعلام عامة مقسمة إلى صفحات تقبل المعلمات التالية: filter و projection و sort و page و pageSize

📖 ملخص


📝 تمارين

  1. أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
  2. قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
  3. راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
  4. التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
  5. حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%