MongoDB: حذف المستندات والبدء في استخدام Node.js

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

يُعد حذف المستندات عملية أساسية في عملية تنقية البيانات — كما تقدم هذه الدورة التدريبية تدريبات عملية على استخدام Node.js وMongoose.

إتقان استخدام deleteOne/deleteMany، والربط بين Node.js وMongoose، وتعريف المخطط، وعمليات CRUD للنموذج.

1. ما ستتعلمه



2. قصة حقيقية لمهندس متكامل

(1) المشكلة: يتطلب تنظيف البيانات منتهية الصلاحية استخدام نصوص برمجية معقدة

يتعين على بوب أن يقوم بانتظام بحذف الطلبات منتهية الصلاحية وحسابات المستخدمين المعطلة على منصة التجارة الإلكترونية:

JAVASCRIPT
// ❌ Counterexample:Query multiple times, then delete(Slow + Race Condition)
const expiredOrders = await Order.find({ expiryDate: { $lt: new Date() } });
for (const order of expiredOrders) {
  await Order.deleteOne({ _id: order._id });
}
// N Sub-network round trip,N Second deletion operation

(2) حل لحذف مجموعات من السجلات في MongoDB + Mongoose

JAVASCRIPT
// ✅ Correct Example:Bulk Deletion in One Go
const result = await Order.deleteMany({
  expiryDate: { $lt: new Date() }
});
// Delete all expired orders in a single operation

// mongoose Schema Definition
const OrderSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, required: true },
  items: [{ sku: String, qty: Number, price: mongoose.Schema.Types.Decimal128 }],
  total: { type: mongoose.Schema.Types.Decimal128, required: true },
  status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered'], default: 'pending' },
  expiryDate: Date,
  createdAt: { type: Date, default: Date.now }
}, { timestamps: true });

const Order = mongoose.model('Order', OrderSchema);

100%
graph LR
    A[Delete Operation] --> B[deleteOne<br/>Delete a single item]
    A --> C[deleteMany<br/>Bulk Delete]
    A --> D[findOneAndDelete<br/>Atom Returns]
    A --> E[drop<br/>Delete Set]
    A --> F[dropDatabase<br/>Delete the database]

    style D fill:#d4edda

3. deleteOne: حذف مستند واحد

شرح المفهوم: deleteOne يحذف المستند الأول الذي يتطابق مع معايير التصفية؛ وهذه هي الطريقة الأساسية للحذف في MongoDB. عملية الحذف لا رجعة فيها — فلا توجد آلية «سلة المحذوفات»، ولا يمكن استعادة المستندات المحذوفة مباشرةً (ما لم يتوفر نسخة احتياطية أو سجل عمليات [oplog]). ولذلك، يجب تنفيذ عمليات الحذف بحذر شديد في بيئات الإنتاج.

كيفية العمل: deleteOne يتبع سير التنفيذ الخطوات التالية: مرحلة المطابقة (البحث عن المستند الأول بناءً على معيار التصفية) → مرحلة الحذف (إزالة المستند من المجموعة) → تحديث الفهرس (حذف إدخالات الفهرس ذات الصلة) → تأكيد «Write Concern». تعتبر العملية بأكملها عملية متكاملة بالنسبة لمستند واحد. بعد الحذف، لا يتم تحرير مساحة القرص على الفور، بل يتم تمييزها على أنها مساحة قابلة لإعادة الاستخدام.

100%
sequenceDiagram
    participant App as Applications
    participant Mongo as MongoDB
    participant WT as WiredTiger

    App->>Mongo: deleteOne({ sku: "PHONE-001" })
    Mongo->>Mongo: Match filter (Index Scan)
    Mongo->>WT: Delete Document + Update Index
    WT-->>Mongo: Confirm Deletion
    Mongo-->>App: { acknowledged: true, deletedCount: 1 }
المعلمة النوع الوصف
filter المستند معايير البحث (إلزامية)
options المستند writeConcern وما إلى ذلك (اختياري)
حقل الإرجاع النوع الوصف
acknowledged منطقية ما إذا كانت عملية الكتابة قد تم تأكيدها أم لا
deletedCount الرقم عدد المستندات المحذوفة (0 أو 1)
JAVASCRIPT
// === deleteOne Basic Usage ===
db.products.deleteOne({ sku: 'PHONE-001' });

// Return Results:
// { acknowledged: true, deletedCount: 1 }
حقل الإرجاع المعنى
acknowledged هل تم تأكيد ذلك؟
deletedCount عدد المستندات المحذوفة (0 أو 1)

▶ المثال 1: استخدام deleteOne عمليًّا

JAVASCRIPT
// === Delete Specified _id the document ===
db.users.deleteOne({ _id: ObjectId('507f1f77bcf86cd799439011') });

// === Delete Based on Specified Criteria(First match)===
db.logs.deleteOne({ level: 'debug' });

// === mongoose Equivalent ===
const result = await Product.deleteOne({ sku: 'PHONE-001' });
console.log(result.deletedCount);  // 1

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, deletedCount: 1 }
{ acknowledged: true, deletedCount: 1 }
1


4. deleteMany: الحذف الجماعي

وصف المفهوم: تقوم deleteMany بحذف جميع المستندات التي تتطابق مع معايير التصفية، وهي الطريقة الأساسية لتنظيف البيانات دفعة واحدة. وعلى عكس deleteOne، التي تحذف المستند الأول المطابق فقط، يمكن لـ deleteMany حذف عشرات الآلاف من المستندات دفعة واحدة. وتشمل حالات الاستخدام الشائعة تنظيف السجلات منتهية الصلاحية، وحذف بيانات المستخدمين المعطلين، وإزالة بيانات الاختبار.

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

البعد deleteOne deleteMany
نطاق التطابق أول تطابق جميع التطابقات
عدد عمليات الحذف 0 أو 1 من 0 إلى N
حالات الاستخدام حذف عنصر واحد التنظيف الجماعي
المخاطر منخفضة متوسطة (تأثير كبير ناجم عن أخطاء المستخدم)
JAVASCRIPT
// === deleteMany Basic Usage ===
db.products.deleteMany({ category: 'Discontinued' });

// === Delete all expired orders ===
db.orders.deleteMany({
  expiryDate: { $lt: new Date() }
});

// === Delete Multiple Documents That Meet Specific Criteria ===
db.logs.deleteMany({
  level: { $in: ['debug', 'info'] },
  createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
});

▶ المثال 2: استخدام deleteMany في الممارسة العملية

JAVASCRIPT
// === Cleanup 30 Entries from a few days ago ===
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const result = await Log.deleteMany({ createdAt: { $lt: thirtyDaysAgo } });
console.log(`Deleted ${result.deletedCount} old logs`);

// === Delete the session of a logged-out user ===
await Session.deleteMany({ userId: deletedUserId });

// === Delete all documents from the entire collection(Use with caution!)===
db.products.deleteMany({});
// ⚠️ This will delete products All documents in the collection

الإخراج:

TEXT 📖 للعرض فقط
Deleted 1542 old logs
{ acknowledged: true, deletedCount: 1542 }
{ acknowledged: true, deletedCount: 1250 }


5. الإرجاع الذري لـ findOneAndDelete

وصف المفهوم: findOneAndDelete هي طريقة حذف خاصة تعيد محتوى المستند المحذوف في نفس الوقت الذي يتم فيه حذفه. وهذا يحل مشكلة التنافس المرتبطة بـ «الاستعلام أولاً، ثم الحذف» — حيث تتطلب الطريقة التقليدية أولاً findOne استرداد المستند ثم deleteOne حذفه، وخلال هذه الفترة قد يتم تعديل المستند أو حذفه بواسطة عمليات أخرى. تجمع findOneAndDelete بين الاستعلام والحذف في عملية واحدة متكاملة.

كيفية العمل: تقوم findOneAndDelete بإجراء عملية متكاملة على مستوى المستند: تحديد موقع المستند المطابق → تسجيل محتوى المستند → حذف المستند → إرجاع المحتوى المسجل. بشكل افتراضي، تُرجع الحالة التي كان عليها المستند قبل الحذف؛ ويمكن استخدام الخيار projection للتحكم في الحقول التي يتم إرجاعها.

100%
graph TB
    A[Need to delete and retrieve a document] --> B{Method Selection}
    B --> C[❌ Check First, Then Delete<br/>findOne + deleteOne<br/>Competitive Conditions Risk]
    B --> D[✅ findOneAndDelete<br/>Atomic Manipulation<br/>No risk of competition]
    B --> E[✅ findOneAndDelete + sort<br/>Atomic Manipulation + Order<br/>FIFO Queue]

    style D fill:#d4edda
    style E fill:#d4edda
الميزة الوصف
الوحدة يتم تنفيذ الاستعلام والحذف في عملية واحدة، مما يمنع حدوث حالات التنافس
استعادة المستند استرداد المستند المحذوف مباشرةً، دون الحاجة إلى استعلام ثانوي
دعم الفرز يتيح الاستهلاك المرتب عند استخدامه مع الخيار sort
حالات الاستخدام مهام قائمة الانتظار، استهلاك الرسائل، خصم المخزون
JAVASCRIPT
// === findOneAndDelete Atomic Manipulation ===
const deletedDoc = db.products.findOneAndDelete({ sku: 'PHONE-001' });

// Restore Deleted Documents(Status before default deletion)
console.log(deletedDoc);
// { _id: ..., sku: 'PHONE-001', title: 'Phone', price: 599, ... }

// === Return if it does not exist null ===
const result = db.products.findOneAndDelete({ sku: 'NOT_EXIST' });
console.log(result);  // null
الميزة الوصف
الوحدة يتم تنفيذ الاستعلام والحذف في عملية واحدة، مما يمنع حدوث حالات التنافس
استعادة المستند استرداد المستند المحذوف مباشرةً، دون الحاجة إلى استعلام ثانوي
حالات الاستخدام مهام قائمة الانتظار، استهلاك الرسائل، خصم المخزون

▶ المثال 3: استخدام findOneAndDelete عمليًّا

JAVASCRIPT
// === Scene:Message Queue(FIFO)===
const message = await Queue.findOneAndDelete(
  { status: 'pending' },
  { sort: { createdAt: 1 } }  // Consume the oldest ones first
);

// === Scene:Claim a Mission ===
const task = await Task.findOneAndDelete({
  status: 'available',
  assignee: null
});
if (task) {
  console.log(`Claimed task: ${task._id}`);
}

// === mongoose Equivalent ===
const message = await Queue.findOneAndDelete(
  { status: 'pending' },
  { sort: { createdAt: 1 } }
);

الإخراج:

TEXT 📖 للعرض فقط
{ _id: ObjectId('...'), status: 'pending', message: 'Task 1', createdAt: ISODate('2026-07-01T10:00:00Z') }
Claimed task: ObjectId('...')
{ _id: ObjectId('...'), status: 'pending', message: 'Queue message', createdAt: ISODate('2026-07-01T09:00:00Z') }


6. drop Collection و dropDatabase

شرح مفاهيمي: تعد drop وdropDatabase أكثر عمليات الحذف شمولاً — حيث تحذف drop مجموعة كاملة (بما في ذلك جميع المستندات والفهارس)، بينما تحذف dropDatabase قاعدة البيانات بأكملها (بما في ذلك جميع المجموعات). وعلى عكس deleteMany({})، فإن عملية drop لا تحذف البيانات فحسب، بل تحذف أيضًا البيانات الوصفية للمجموعة (تعريفات الفهارس، وقواعد التحقق من صحة المخطط، وإعدادات الحد الأقصى، وما إلى ذلك).

التحليل المقارن:

البعد deleteMany({}) drop() dropDatabase()
نطاق الحذف جميع المستندات الموجودة في المجموعة المجموعة بأكملها قاعدة البيانات بأكملها
الاحتفاظ بالفهرس ✅ الاحتفاظ ❌ حذف الكل ❌ حذف الكل
الاحتفاظ بإعداد "capped" ✅ الاحتفاظ ❌ الحذف ❌ الحذف
الاحتفاظ بالتحقق من صحة المخطط ✅ الاحتفاظ ❌ الحذف ❌ الحذف
السرعة بطيئة (تحذف الملفات واحدًا تلو الآخر) سريعة (تحرر المساحة على الفور) سريعة
إمكانية الاسترداد يمكن استردادها عبر سجل العمليات (oplog) من الصعب للغاية استردادها من الصعب للغاية استردادها
JAVASCRIPT
// === Delete Set ===
db.products.drop();
// true(Success)or false(The set does not exist)

// === Delete the database ===
db.dropDatabase();
// { "dropped" : "shopdb", "ok" : 1 }

// === Use with caution: Delete all data from the entire collection but keep the collection itself ===
db.products.deleteMany({});
// Equivalent but preserves the set structure(Index、capped Settings)

▶ المثال 4: الاختيار بين drop وdeleteMany

JAVASCRIPT
// Scene:Cleaning Up Temporary Test Sets
// ✅ Recommendations:drop(Delete Set+Index,Clean)
db.test_results.drop();

// ✅ Preserve the collection structure:deleteMany(Clear the document only)
db.user_sessions.deleteMany({});

الإخراج:

TEXT 📖 للعرض فقط
true
{ "dropped": "shopdb", "ok": 1 }
{ acknowledged: true, deletedCount: 0 }


7. Node.js + Mongoose: الاتصال بقاعدة بيانات MongoDB

نظرة عامة على المفهوم: يُعد Mongoose أكثر نماذج ODM (نمذجة الوثائق الكائنية) شيوعًا لـ MongoDB في نظام Node.js، حيث يوفر ميزات متقدمة مثل تعريف المخطط، والتحقق من صحة البيانات، والبرمجيات الوسيطة، واستعلامات الربط. يبدأ هذا القسم بالاتصال بـ MongoDB، ثم يقدم تدريجيًا المفاهيم الأساسية لـ Mongoose.

كيفية العمل: تتألف العملية التي يتبعها Mongoose للاتصال بـ MongoDB من الخطوات التالية: إنشاء مثيل اتصال → إقامة اتصال TCP → المصادقة (إذا لزم الأمر) → اختيار قاعدة البيانات → تهيئة مجموعة الاتصالات → تشغيل الحدث connected. بشكل افتراضي، تحتفظ Mongoose بمجموعة اتصالات (عادةً ما تتراوح بين 5 و100 اتصال) وتعيد استخدام الاتصالات لتجنب إنشاء وإغلاق اتصالات TCP بشكل متكرر.

100%
sequenceDiagram
    participant App as Node.js Applications
    participant Mongoose as mongoose
    participant Mongo as MongoDB

    App->>Mongoose: mongoose.connect(uri)
    Mongoose->>Mongo: Establish TCP Connect
    Mongo-->>Mongoose: Connection Confirmation
    Mongoose->>Mongo: Certification(If you need)
    Mongo-->>Mongoose: Authentication Successful
    Mongoose->>Mongoose: Initialize the connection pool
    Mongoose-->>App: Trigger 'connected' Event
    Note over App,Mongoose: Connection Ready,Executable CRUD
طريقة الاتصال تنسيق URI حالات الاستخدام
برنامج محلي مستقل mongodb://localhost:27017/shopdb بيئة التطوير
Atlas Cloud mongodb+srv://user:pass@cluster0.mongodb.net/mydb الإنتاج/الفريق
مجموعة المثيلات mongodb://host1,host2,host3/shopdb?replicaSet=rs0 بيئة الإنتاج
Docker mongodb://192.168.1.100:27017/shopdb النشر باستخدام الحاويات

(1) تثبيت Mongoose

BASH
npm install mongoose --save

(2) الاتصال بـ MongoDB

JAVASCRIPT
// === Basic Connections ===
const mongoose = require('mongoose');

async function connectDB() {
  await mongoose.connect('mongodb://localhost:27017/shopdb');
  console.log('✅ MongoDB connected');
}

connectDB().catch(err => console.error('❌ Connection error:', err));

(3) خيارات الاتصال

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

وصف المعلمات الرئيسية:

المعلمة القيمة الافتراضية الوصف التوصية الخاصة بالإنتاج
serverSelectionTimeoutMS 30000 مهلة اختيار الخادم (بالميلي ثانية) 5000
socketTimeoutMS 30,000 انتهت مهلة الاتصال 45,000
maxPoolSize 100 الحد الأقصى لحجم مجموعة الاتصالات 50–100
minPoolSize 0 الحد الأدنى لحجم مجموعة الاتصالات 5
heartbeatFrequencyMS 10,000 تردد مراقبة معدل ضربات القلب 10,000
retryWrites صحيح إعادة المحاولة التلقائية للكتابة صحيح
authSource admin قاعدة بيانات المصادقة حسب الإعدادات

مبدأ تجميع الاتصالات: تحتفظ Mongoose بمجموعة من اتصالات TCP لتجنب العبء الناتج عن إنشاء الاتصالات وإغلاقها بشكل متكرر. يسترد كل طلب متزامن اتصالاً من المجموعة ويعيده بمجرد اكتمال الطلب. maxPoolSize تحكم في الحد الأقصى لعدد الاتصالات المتزامنة — فالتعيين المنخفض جدًّا لهذه القيمة سيؤدي إلى تراكم الطلبات في قائمة الانتظار، بينما سيؤدي التعيين المرتفع جدًّا إلى استهلاك موارد الخادم بشكل مفرط.

JAVASCRIPT
// === Complete Connection Configuration ===
await mongoose.connect('mongodb://localhost:27017/shopdb', {
  // Server selection timeout
  serverSelectionTimeoutMS: 5000,

  // Socket Timeout
  socketTimeoutMS: 45000,

  // Connection Pool Size
  maxPoolSize: 50,
  minPoolSize: 5,

  // Automatic Reconnection
  autoReconnect: true,

  // Certification(If enabled)
  user: 'admin',
  pass: 'password',

  // Certification Database
  authSource: 'admin'
});

(4) الاتصال بـ Atlas

JAVASCRIPT
// === Atlas Concatenate Strings ===
await mongoose.connect(
  'mongodb+srv://user:pass@cluster0.mongodb.net/mydb?retryWrites=true&w=majority'
);

// === With environment variables ===
require('dotenv').config();
await mongoose.connect(process.env.MONGODB_URI);

▶ المثال 5: إدارة الاتصال الكاملة

JAVASCRIPT
// db.js
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/shopdb', {
      serverSelectionTimeoutMS: 5000,
      maxPoolSize: 50
    });
    console.log(`✅ MongoDB connected: ${conn.connection.host}`);

    // Listening for Connection Events
    mongoose.connection.on('error', (err) => console.error('❌ MongoDB error:', err));
    mongoose.connection.on('disconnected', () => console.warn('⚠️ MongoDB disconnected'));
    mongoose.connection.on('reconnected', () => console.log('🔄 MongoDB reconnected'));
  } catch (err) {
    console.error('❌ Connection failed:', err.message);
    process.exit(1);
  }
};

const disconnectDB = async () => {
  await mongoose.disconnect();
  console.log('MongoDB disconnected');
};

module.exports = { connectDB, disconnectDB, mongoose };

الإخراج:

TEXT 📖 للعرض فقط
✅ MongoDB connected: localhost
🔄 MongoDB reconnected
MongoDB disconnected


8. تعريف مخطط Mongoose

شرح المفهوم: يُعد «المخطط» (schema) مفهومًا أساسيًّا في Mongoose — فهو يحدد أنواع حقول المستند، وقواعد التحقق من الصحة، والقيم الافتراضية، والفهارس، وغير ذلك. وعلى الرغم من أن MongoDB نفسها لا تعتمد على مخطط، فإن Mongoose توفر قيودًا للمخطط على مستوى طبقة التطبيق لمنع وجود بيانات غير صحيحة وأخطاء في الأنواع. وبمجرد تعريف المخطط، يتم ترجمته إلى نموذج (model)، والذي يعمل كواجهة للتفاعل مع قاعدة البيانات.

كيفية العمل: «المخطط → النموذج → المستند» هي بنية مونغوس ثلاثية المستويات. يحدد المخطط البنية (أنواع الحقول والتحقق من الصحة)؛ أما النموذج فهو النتيجة المُجمَّعة للمخطط (وهو ما يقابل «المجموعة»)؛ والمستند هو مثيل للنموذج (وهو ما يقابل «المستند»). لا يتفاعل المخطط مباشرةً مع قاعدة البيانات؛ ولا يمكن تنفيذ عمليات CRUD إلا من خلال النموذج.

خيارات المخطط: تتحكم المعلمة الثانية لمُنشئ المخطط في السلوك العام — حيث تقوم timestamps: true بإضافة createdAt/updatedAt تلقائيًا، بينما تتجاهل strict: true الحقول غير المُعلنة، وتقوم versionKey: false بإزالة مفتاح الإصدار __v.

100%
graph LR
    A[Schema<br/>Define Field Types+Verification] -->|mongoose.model| B[Model<br/>Interfaces for Operation Sets]
    B -->|new Model| C[Document<br/>An Example Document]
    B -->|Model.find| D[Search Results<br/>Document Array]

    style A fill:#cce5ff
    style B fill:#d4edda
خيارات حقول المخطط النوع الوصف مثال
type مُنشئ نوع الحقل String، Number، Date
required منطقية/مصفوفة مطلوب [true, 'Email is required']
default أي/وظيفة القيمة الافتراضية Date.now، 0، true
unique منطقية تحديد ما إذا كان سيتم إنشاء فهرس فريد true
index منطقية/كائن إنشاء فهرس true، { sparse: true }
enum مصفوفة قائمة القيم المسموح بها ['pending', 'paid']
min / max الرقم نطاق القيمة min: 0, max: 999999
minlength / maxlength الرقم نطاق طول السلسلة minlength: 3
match RegExp التحقق من صحة التعبيرات النمطية /^.+@.+$/
select منطقية ما إذا كان الاستعلام الافتراضي يُرجع false (مثل passwordHash)
validate الدالة دالة التحقق المخصصة v => v.length >= 8
get / set الدالة دالة الحصول/التعيين الافتراضية get: v => v.toString()

(1) المخطط الأساسي

JAVASCRIPT
// === Schema Defining the User Model ===
const UserSchema = new mongoose.Schema({
  // Field Definitions
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    trim: true
  },
  username: {
    type: String,
    required: true,
    unique: true,
    minlength: 3,
    maxlength: 30
  },
  passwordHash: {
    type: String,
    required: true,
    select: false  // The default query returns no results.
  },
  age: {
    type: Number,
    min: 0,
    max: 150
  },
  role: {
    type: String,
    enum: ['customer', 'admin', 'moderator'],
    default: 'customer'
  },
  isActive: {
    type: Boolean,
    default: true
  }
}, {
  // Schema Options
  timestamps: true,           // Auto-add createdAt/updatedAt
  collection: 'users',       // Explicitly Specify the Set Name
  strict: true,              // Strict Mode(Do not save undeclared fields)
  versionKey: false          // Disable __v
});

(2) أنواع المخططات

JAVASCRIPT
const ProductSchema = new mongoose.Schema({
  // String
  sku: String,

  // Numbers
  stock: Number,
  price: mongoose.Schema.Types.Decimal128,

  // Date
  releaseDate: Date,

  // Boolean
  isActive: Boolean,

  // Array
  tags: [String],

  // Nested Documents
  specs: {
    screen: String,
    battery: String
  },

  // Buffer(Binary)
  thumbnail: Buffer,

  // ObjectId Quote
  categoryId: mongoose.Schema.Types.ObjectId,

  // Mixed Type(Any)
  metadata: mongoose.Schema.Types.Mixed,

  // Map(Key-value pairs)
  translations: {
    type: Map,
    of: String
  }
});

▶ المثال 6: تصميم مخطط شامل

JAVASCRIPT
const ProductSchema = new mongoose.Schema({
  sku: {
    type: String,
    required: [true, 'SKU is required'],
    unique: true,
    index: true,
    match: /^[A-Z0-9-]+$/
  },
  title: {
    type: String,
    required: true,
    trim: true,
    maxlength: 200
  },
  description: {
    type: String,
    maxlength: 5000
  },
  price: {
    type: mongoose.Schema.Types.Decimal128,
    required: true,
    min: 0,
    get: v => v ? v.toString() : v  // Serialize to a string
  },
  category: {
    type: String,
    enum: ['Electronics', 'Books', 'Clothing', 'Home'],
    required: true,
    index: true
  },
  tags: [String],
  attributes: {
    type: Map,
    of: mongoose.Schema.Types.Mixed,
    default: {}
  },
  stock: {
    type: Number,
    default: 0,
    min: 0
  },
  rating: {
    type: Number,
    default: 0,
    min: 0,
    max: 5
  },
  isActive: {
    type: Boolean,
    default: true,
    index: true
  }
}, {
  timestamps: true,
  toJSON: { virtuals: true, getters: true },
  toObject: { virtuals: true }
});

// Virtual Fields
ProductSchema.virtual('isInStock').get(function() {
  return this.stock > 0;
});

// Index
ProductSchema.index({ category: 1, price: 1 });
ProductSchema.index({ title: 'text', description: 'text' });

const Product = mongoose.model('Product', ProductSchema);

الإخراج:

TEXT 📖 للعرض فقط
// Schema created successfully
// Indexes: { category: 1, price: 1 }, { title: 'text', description: 'text' }
// Virtual field isInStock added


9. عمليات CRUD في Mongoose

شرح المفهوم: CRUD (إنشاء/قراءة/تحديث/حذف) هو النمط الأساسي لعمليات قاعدة البيانات. توفر Mongoose نمطين لعمليات CRUD — الطرق الثابتة للنموذج (مثل Model.create() وModel.find()) والطرق الخاصة بمثيلات المستند (مثل doc.save() وdoc.remove()). تعمل الطرق الثابتة مباشرةً على قاعدة البيانات، بينما تقوم طرق المثيل أولاً بتعديل الكائن الموجود في الذاكرة ثم مزامنة التغييرات مع قاعدة البيانات.

التحليل المقارن:

البعد الطرق الثابتة للنموذج طرق مثيل المستند
طريقة الاستدعاء Model.create(data) new Model(data); doc.save()
التحقق من المشغل
البرمجيات الوسيطة التي يتم تشغيلها جزئي ✅ الكل
قيمة الإرجاع مستند أو كائن نتيجة مستند
حالات الاستخدام عمليات CRUD البسيطة منطق الأعمال المعقد
100%
graph TB
    A[mongoose CRUD] --> B[Create<br/>create() / save()]
    A --> C[Read<br/>find() / findOne() / findById()]
    A --> D[Update<br/>updateOne() / findByIdAndUpdate()]
    A --> E[Delete<br/>deleteOne() / findByIdAndDelete()]

    style A fill:#cce5ff

(1) إنشاء

JAVASCRIPT
// === model.create() Create a Single Document ===
const user = await User.create({
  email: 'alice@example.com',
  username: 'alice_chen',
  passwordHash: 'hashed_password',
  age: 28
});
console.log(user._id);  // ObjectId

// === Create Multiple Documents ===
const users = await User.create([
  { email: 'bob@example.com', username: 'bob' },
  { email: 'charlie@example.com', username: 'charlie' }
]);

// === new + save Pattern ===
const user = new User({
  email: 'alice@example.com',
  username: 'alice_chen'
});
await user.save();

(2) اقرأ

JAVASCRIPT
// === find Search for multiple ===
const users = await User.find({ isActive: true });

// === findOne Query a single ===
const user = await User.findOne({ email: 'alice@example.com' });

// === findById Through _id Search ===
const user = await User.findById('507f1f77bcf86cd799439011');

// === Chain Query ===
const products = await Product.find({ category: 'Electronics' })
  .select('sku title price')
  .sort({ price: 1 })
  .limit(20)
  .lean();

(3) تحديث

JAVASCRIPT
// === findByIdAndUpdate ===
const user = await User.findByIdAndUpdate(
  userId,
  { $set: { lastLoginAt: new Date() } },
  { new: true, runValidators: true }  // Return to the updated document
);

// === updateOne ===
const result = await User.updateOne(
  { email: 'alice@example.com' },
  { $set: { age: 29 } }
);

// === save() Replace the entire document ===
const user = await User.findById(userId);
user.age = 30;
await user.save();

(4) حذف

JAVASCRIPT
// === findByIdAndDelete ===
const user = await User.findByIdAndDelete(userId);

// === deleteOne ===
const result = await User.deleteOne({ email: 'alice@example.com' });

// === deleteMany ===
const result = await User.deleteMany({ isActive: false });

▶ المثال 7: تمرين عملي كامل على CRUD

JAVASCRIPT
// === 1. Create a User ===
const alice = await User.create({
  email: 'alice@example.com',
  username: 'alice_chen',
  passwordHash: await bcrypt.hash('password123', 10),
  age: 28
});

// === 2. Query User ===
const users = await User.find({ age: { $gte: 18 } })
  .select('email username age')
  .lean();

// === 3. Update User ===
await User.updateOne(
  { _id: alice._id },
  { $set: { lastLoginAt: new Date() }, $inc: { loginCount: 1 } }
);

// === 4. Delete Test User ===
await User.deleteMany({ email: { $regex: '@test\\.com$' } });

الإخراج:

TEXT 📖 للعرض فقط
{
  _id: ObjectId('...'),
  email: 'alice@example.com',
  username: 'alice_chen',
  age: 28,
  createdAt: ISODate('2026-07-01T10:00:00Z'),
  updatedAt: ISODate('2026-07-01T10:00:00Z')
}
[
  { _id: ObjectId('...'), email: 'alice@example.com', username: 'alice_chen', age: 28 },
  { _id: ObjectId('...'), email: 'bob@example.com', username: 'bob', age: 25 }
]
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, deletedCount: 5 }


10. حل المشكلات الشائعة

نظرة عامة على المفهوم: الأنواع الأربعة الأكثر شيوعًا من الأخطاء التي تصادف عند التطوير باستخدام Node.js وMongoose هي: أخطاء الاتصال (ECONNREFUSED)، وتعارضات الفهارس الفريدة (E11000)، وفشل التحقق من صحة المخطط (ValidationError)، وفشل تحويل الأنواع (CastError). ويُعد فهم الأسباب الجذرية لهذه الأخطاء وكيفية التعامل معها مهارة أساسية في تطوير Mongoose.

استراتيجية معالجة الأخطاء: تتطلب أخطاء الاتصال آلية إعادة المحاولة؛ وتتطلب تعارضات الفهارس الفريدة استخدام UPSERT أو التحقق من صحة البيانات في الواجهة الأمامية؛ وتتطلب حالات فشل التحقق من صحة المخطط تحسين عملية التحقق من صحة النماذج؛ وتتطلب أخطاء CastErrors التحقق من تنسيق ObjectId. يجب اكتشاف جميع الأخطاء في طبقة التطبيق وإرجاع رسائل خطأ سهلة الفهم للمستخدم — لا تكشف عن تتبع مكدس أخطاء MongoDB الخام لمستخدمي الواجهة الأمامية.

100%
graph TB
    A[mongoose Error] --> B[Connection Error<br/>ECONNREFUSED]
    A --> C[Unique Index Conflict<br/>E11000]
    A --> D[Verification Failed<br/>ValidationError]
    A --> E[Type conversion failed<br/>CastError]
    
    B --> B1[Inspection mongod Enable or Disable<br/>Check the connection string]
    C --> C1[Usage upsert<br/>Front-End Uniqueness Validation]
    D --> D1[Improve Schema Verification<br/>Front-End Form Validation]
    E --> E1[Inspection ObjectId Format<br/>Verify Parameter Types]
الخطأ السبب الحل
MongooseServerSelectionError: connect ECONNREFUSED MongoDB غير قيد التشغيل brew services start mongodb-community@7.0
MongoError: E11000 duplicate key error تعارض في الفهرس الفريد التحقق من وجود قيم مكررة في الحقول
ValidationError: Path 'email' is required الحقول الإلزامية غير مملوءة يرجى ملء الحقول الإلزامية
CastError: Cast to ObjectId failed خطأ في تنسيق _id تحقق من تنسيق سلسلة ObjectId


❓ أسئلة شائعة

س أيهما أفضل، deleteOne أم findOneAndDelete؟
ج استخدم findOneAndDelete إذا كنت بحاجة إلى استعادة المستند المحذوف؛ واستخدم deleteOne إذا كنت تريد حذفه فقط. وكلاهما يتمتعان بأداء متكافئ.
س هل يجب إغلاق اتصالات Mongoose؟
ج نعم. استدعِ mongoose.disconnect() أو mongoose.connection.close() عند إنهاء العملية لضمان إغلاق جميع الاتصالات بشكل سليم.
س إذا تم تغيير حقل ما في المخطط، فهل يلزم ترحيل قاعدة البيانات؟
ج لا. يتم تعريف مخطط Mongoose في طبقة التطبيق ولا يؤثر على قاعدة البيانات. قاعدة بيانات MongoDB لا تعتمد على مخطط محدد، لذا تُضاف الحقول الجديدة تلقائيًا. ومع ذلك، قد تفتقر المستندات القديمة إلى الحقول الجديدة، لذا يجب على طبقة التطبيق معالجة هذا الأمر undefined.
س ما هو تأثير lean() على الأداء؟
ج يتخطى lean() عملية تهيئة المستندات في Mongoose ويعيد كائن JavaScript خالصًا. ويؤدي ذلك إلى تحسن في الأداء بمقدار 3 إلى 5 أضعاف، لكنك تفقد إمكانية الوصول إلى أساليب المستندات في Mongoose (مثل save() وpopulate()). وهو مناسب لواجهات برمجة التطبيقات (API) الخاصة بالاستعلامات البحتة.
س ما الفرق بين Model.create وnew Model + save؟
ج كلاهما يُشغّل عملية التحقق من صحة المخطط والبرمجيات الوسيطة. وتعتبر صيغة Model.create() أكثر إيجازًا، في حين أن new + save أكثر ملاءمةً للسيناريوهات التي تتطلب عمليات خطوة بخطوة.

📖 ملخص


📝 تمارين

  1. السؤال الأساسي (⭐): استخدم deleteOne لحذف المنتج الذي يحمل رمز SKU «TEST-001».
  2. الأسئلة الأساسية (⭐): حدد نموذج Product باستخدام Mongoose Schema (بما في ذلك sku وtitle وprice وcategory وstock)، وقم بتنفيذ عمليات create، find وupdate وdelete.
  3. مشكلة متقدمة (⭐⭐): اكتب برنامجًا نصيًّا بلغة Node.js لتنظيف الطلبات منتهية الصلاحية التي مضى عليها أكثر من 30 يومًا بشكل دوري (باستخدام deleteMany).
  4. مشكلة متقدمة (⭐⭐): قم بتنفيذ قائمة انتظار الرسائل: استخدم findOneAndDelete مع sort: { createdAt: 1 } لتنفيذ آلية الاستهلاك وفقًا لمبدأ «الأول يدخل أولاً» (FIFO).
  5. التحدي (⭐⭐⭐): تنفيذ ميزة تسجيل المستخدم باستخدام Mongoose وSchema والبرمجيات الوسيطة (بما في ذلك التحقق من تفرد عنوان البريد الإلكتروني، وتشفير كلمات المرور باستخدام bcrypt، ووضع الطابع الزمني).
Web-Tutorial.com

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

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

100%