MongoDB: حذف المستندات والبدء في استخدام Node.js
آخر تحديث: 2026-08-26
يُعد حذف المستندات عملية أساسية في عملية تنقية البيانات — كما تقدم هذه الدورة التدريبية تدريبات عملية على استخدام Node.js وMongoose.
إتقان استخدام deleteOne/deleteMany، والربط بين Node.js وMongoose، وتعريف المخطط، وعمليات CRUD للنموذج.
1. ما ستتعلمه
- deleteOne / deleteMany: حذف المستندات
- تُرجع الدالة
findOneAndDeleteالمستند المحذوف بشكل متكامل dropلتعيين قاعدة البيانات وdropDatabaseلحذفها- Node.js + Mongoose: الاتصال بقاعدة بيانات MongoDB
- تحديد مخططات Mongoose وإنشاء النماذج
- عمليات CRUD في Mongoose (إنشاء/قراءة/تحديث/حذف)
2. قصة حقيقية لمهندس متكامل
(1) المشكلة: يتطلب تنظيف البيانات منتهية الصلاحية استخدام نصوص برمجية معقدة
يتعين على بوب أن يقوم بانتظام بحذف الطلبات منتهية الصلاحية وحسابات المستخدمين المعطلة على منصة التجارة الإلكترونية:
// ❌ 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
// ✅ 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);
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». تعتبر العملية بأكملها عملية متكاملة بالنسبة لمستند واحد. بعد الحذف، لا يتم تحرير مساحة القرص على الفور، بل يتم تمييزها على أنها مساحة قابلة لإعادة الاستخدام.
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) |
// === deleteOne Basic Usage ===
db.products.deleteOne({ sku: 'PHONE-001' });
// Return Results:
// { acknowledged: true, deletedCount: 1 }
| حقل الإرجاع | المعنى |
|---|---|
acknowledged |
هل تم تأكيد ذلك؟ |
deletedCount |
عدد المستندات المحذوفة (0 أو 1) |
▶ المثال 1: استخدام deleteOne عمليًّا
// === 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 |
| حالات الاستخدام | حذف عنصر واحد | التنظيف الجماعي |
| المخاطر | منخفضة | متوسطة (تأثير كبير ناجم عن أخطاء المستخدم) |
// === 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 في الممارسة العملية
// === 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 للتحكم في الحقول التي يتم إرجاعها.
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 |
| حالات الاستخدام | مهام قائمة الانتظار، استهلاك الرسائل، خصم المخزون |
// === 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 عمليًّا
// === 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) | من الصعب للغاية استردادها | من الصعب للغاية استردادها |
// === 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
// 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 بشكل متكرر.
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
npm install mongoose --save
(2) الاتصال بـ MongoDB
// === 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 تحكم في الحد الأقصى لعدد الاتصالات المتزامنة — فالتعيين المنخفض جدًّا لهذه القيمة سيؤدي إلى تراكم الطلبات في قائمة الانتظار، بينما سيؤدي التعيين المرتفع جدًّا إلى استهلاك موارد الخادم بشكل مفرط.
// === 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
// === 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: إدارة الاتصال الكاملة
// 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.
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) المخطط الأساسي
// === 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) أنواع المخططات
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: تصميم مخطط شامل
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 البسيطة | منطق الأعمال المعقد |
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) إنشاء
// === 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) اقرأ
// === 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) تحديث
// === 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) حذف
// === 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
// === 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 الخام لمستخدمي الواجهة الأمامية.
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.disconnect() أو mongoose.connection.close() عند إنهاء العملية لضمان إغلاق جميع الاتصالات بشكل سليم.undefined.lean() على الأداء؟lean() عملية تهيئة المستندات في Mongoose ويعيد كائن JavaScript خالصًا. ويؤدي ذلك إلى تحسن في الأداء بمقدار 3 إلى 5 أضعاف، لكنك تفقد إمكانية الوصول إلى أساليب المستندات في Mongoose (مثل save() وpopulate()). وهو مناسب لواجهات برمجة التطبيقات (API) الخاصة بالاستعلامات البحتة.Model.create وnew Model + save؟Model.create() أكثر إيجازًا، في حين أن new + save أكثر ملاءمةً للسيناريوهات التي تتطلب عمليات خطوة بخطوة.📖 ملخص
- تُستخدم الدالة
deleteOneلحذف مستند واحد؛ بينما تُستخدم الدالةdeleteManyلحذف عدة مستندات - findOneAndDelete: عملية متجانسة تُرجع المستند الذي تم حذفه؛ مناسبة لقوائم الانتظار واسترجاع المهام
dropيحذف مجموعة؛dropDatabaseيحذف قاعدة بيانات- يتصل «mongoose connect» بقاعدة بيانات MongoDB ويدعم كلاً من Atlas والمثيلات المحلية
- المخطط: يحدد أنواع البيانات، وعمليات التحقق من الصحة، والقيم الافتراضية، والفهارس
- نموذج CRUD: create / find / findOne / findByIdAndUpdate / findByIdAndDelete
- طرق معالجة المستندات في Mongoose: save() / lean() / populate() / toJSON()
📝 تمارين
- السؤال الأساسي (⭐): استخدم
deleteOneلحذف المنتج الذي يحمل رمز SKU «TEST-001». - الأسئلة الأساسية (⭐): حدد نموذج
Productباستخدام Mongoose Schema (بما في ذلكskuوtitleوpriceوcategoryوstock)، وقم بتنفيذ عملياتcreate،findوupdateوdelete. - مشكلة متقدمة (⭐⭐): اكتب برنامجًا نصيًّا بلغة Node.js لتنظيف الطلبات منتهية الصلاحية التي مضى عليها أكثر من 30 يومًا بشكل دوري (باستخدام
deleteMany). - مشكلة متقدمة (⭐⭐): قم بتنفيذ قائمة انتظار الرسائل: استخدم
findOneAndDeleteمعsort: { createdAt: 1 }لتنفيذ آلية الاستهلاك وفقًا لمبدأ «الأول يدخل أولاً» (FIFO). - التحدي (⭐⭐⭐): تنفيذ ميزة تسجيل المستخدم باستخدام Mongoose وSchema والبرمجيات الوسيطة (بما في ذلك التحقق من تفرد عنوان البريد الإلكتروني، وتشفير كلمات المرور باستخدام bcrypt، ووضع الطابع الزمني).