كيفية استخدام MongoDB و Mongoose مع Node.js: أفضل الممارسات لمطوري الواجهة الخلفية
MongoDB بلا شك واحدة من أشهر قواعد بيانات NoSQL وأكثرها شيوعًا اليوم، وتتمتع بمجتمع وبيئة عمل مزدهرة. في هذا المقال، سنستعرض مجموعة من أفضل الممارسات التي يجب اتباعها عند إعداد MongoDB و Mongoose مع بيئة تشغيل Node.js لتطوير الواجهة الخلفية.
متطلبات مسبقة لفهم هذا المقال
يُعد هذا المقال جزءًا من مسار تعلم الواجهة الخلفية في codedamn، والذي يبدأ بأساسيات الواجهة الخلفية ويغطيها بالتفصيل. لذلك، نفترض أن لديك بعض الخبرة في JavaScript و Node.js بالفعل. حاليًا، نحن في هذه المرحلة من المسار:

إذا كانت لديك خبرة قليلة جدًا في Node.js/JavaScript أو الواجهة الخلفية بشكل عام، فقد يكون هذا مكانًا جيدًا للبدء. يمكنك أيضًا العثور على دورة مجانية حول Mongoose + MongoDB + Node.js هنا. الآن، دعنا نتعمق في الموضوع.
لماذا تحتاج إلى Mongoose في تطبيقاتك؟
لفهم سبب حاجتنا إلى Mongoose، دعنا نفهم كيف تعمل MongoDB (وقواعد البيانات بشكل عام) على مستوى البنية المعمارية.
- لديك خادم قاعدة بيانات (مثل خادم مجتمع
MongoDB). - لديك نص برمجي لـ
Node.jsيعمل كعملية.
يستمع خادم MongoDB عادةً على مقبس TCP، ويمكن لعملية Node.js الخاصة بك الاتصال به باستخدام اتصال TCP. ولكن فوق طبقة TCP، تمتلك MongoDB أيضًا بروتوكولها الخاص لفهم ما يريده العميل (عملية Node.js الخاصة بنا) من قاعدة البيانات بالضبط.
لتبسيط هذا الاتصال، بدلاً من تعلم الرسائل التي يجب إرسالها على طبقة TCP، نقوم بتجريد ذلك بمساعدة برنامج “مشغل” (driver)، والذي يُطلق عليه في هذه الحالة “مشغل MongoDB” (MongoDB driver). يتوفر مشغل MongoDB كحزمة npm هنا.
تذكر أن مشغل MongoDB مسؤول عن الاتصال وتجريد طلبات/استجابات الاتصال منخفضة المستوى منك – ولكن هذا لا يأخذك بعيدًا كـ مطور. نظرًا لأن MongoDB هي قاعدة بيانات بلا مخطط (schemaless)، فإنها تمنحك قوة أكبر بكثير مما تحتاجه كمبتدئ. المزيد من القوة يعني مساحة أكبر لارتكاب الأخطاء. أنت بحاجة إلى تقليل مساحة الأخطاء والمشاكل التي يمكن أن تحدث في التعليمات البرمجية الخاصة بك. أنت بحاجة إلى شيء أكثر.
مقدمة إلى Mongoose
Mongoose هي طبقة تجريدية فوق مشغل MongoDB الأصلي (حزمة npm التي ذكرتها أعلاه). القاعدة العامة للتجريدات (كما أفهمها) هي أنك مع كل تجريد تفقد بعض قوة العمليات منخفضة المستوى. لكن هذا لا يعني بالضرورة أنه سيئ. في بعض الأحيان يعزز الإنتاجية بشكل كبير لأنه لا تحتاج أبدًا إلى الوصول الكامل إلى الواجهة البرمجية الأساسية (API) على أي حال.
طريقة جيدة للتفكير في الأمر هي أنك تنشئ تطبيق دردشة في الوقت الفعلي باستخدام C و Python. سيكون مثال Python أسهل وأسرع بكثير بالنسبة لك كمطور للتنفيذ بإنتاجية أعلى. قد يكون C أكثر كفاءة، لكنه سيكلف الكثير من حيث الإنتاجية/سرعة التطوير/الأخطاء/التعطلات. بالإضافة إلى ذلك، في معظم الأحيان لا تحتاج إلى القوة التي يمنحك إياها C لتنفيذ websockets.
وبالمثل، مع Mongoose، يمكنك تقييد مساحة الوصول إلى API منخفضة المستوى، ولكن تطلق العنان للكثير من المكاسب المحتملة وتجربة مطور ممتازة (DX).
كيفية الاتصال بقاعدة بيانات MongoDB باستخدام Mongoose
أولاً، دعنا نرى بسرعة كيف يجب أن تتصل بقاعدة بيانات MongoDB الخاصة بك باستخدام Mongoose:
mongoose.connect(DB_CONNECTION_STRING, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
})
يضمن تنسيق الاتصال هذا أنك تستخدم محلل URL الجديد من Mongoose، وأنك لا تستخدم أي ممارسات مهملة (deprecated). يمكنك قراءة المزيد بالتفصيل عن جميع رسائل الإهمال هذه هنا إذا أردت.
كيفية تنفيذ العمليات الأساسية باستخدام Mongoose
دعنا الآن نناقش بسرعة العمليات باستخدام Mongoose، وكيف يجب عليك تنفيذها. يمنحك Mongoose خيارات لنوعين من الاستعلامات:
- استعلامات تعتمد على المؤشر (
Cursor-based querying) - استعلامات جلب كاملة (
Full fetching query)
استعلامات تعتمد على المؤشر (Cursor-based querying)
تعني الاستعلامات التي تعتمد على المؤشر أنك تعمل مع سجل واحد في كل مرة بينما تقوم بجلب مستند واحد أو دفعة من المستندات في كل مرة من قاعدة البيانات. هذه طريقة فعالة للعمل مع كميات هائلة من البيانات في بيئة ذات ذاكرة محدودة. تخيل أن عليك تحليل مستندات بحجم إجمالي 10 جيجابايت على خادم سحابي بذاكرة 1 جيجابايت/معالج واحد. لا يمكنك جلب المجموعة بأكملها لأنها لن تتناسب مع نظامك. المؤشر (Cursor) هو خيار جيد (والوحيد؟) هنا.
استعلامات الجلب الكاملة (Full fetching query)
هذا هو نوع الاستعلام الذي تحصل فيه على الاستجابة الكاملة لاستعلامك دفعة واحدة. في معظم الأحيان، هذا هو ما ستستخدمه. لذلك، سنركز بشكل أساسي على هذه الطريقة هنا.
استخدام نماذج Mongoose (Models)
تعد النماذج (Models) القوة الخارقة لـ Mongoose. إنها تساعدك على فرض قواعد “المخطط” (schema) وتوفر تكاملًا سلسًا لتعليمات Node.js البرمجية الخاصة بك في استدعاءات قاعدة البيانات. الخطوة الأولى هي تحديد نموذج جيد:
import mongoose from 'mongoose'
const CompletedSchema = new mongoose.Schema(
{
type: {
type: String,
enum: ['course', 'classroom'],
required: true
},
parentslug: {
type: String,
required: true
},
slug: {
type: String,
required: true
},
userid: {
type: String,
required: true
}
},
{ collection: 'completed' }
)
CompletedSchema.index({ slug: 1, userid: 1 }, { unique: true })
const model = mongoose.model('Completed', CompletedSchema)
export default model
هذا مثال مختصر ومباشر من قاعدة بيانات codedamn. إليك بعض النقاط المثيرة للاهتمام التي يجب ملاحظتها هنا:
- حاول إبقاء
required: trueعلى جميع الحقول المطلوبة. يمكن أن يوفر لك هذا الكثير من المتاعب إذا كنت لا تستخدم نظامًا للتحقق من النوع الثابت مثلTypeScriptلمساعدتك في أسماء الخصائص الصحيحة عند إنشاء كائن. بالإضافة إلى أن التحقق المجاني رائع أيضًا. - حدد الفهارس (
indexes) والحقول الفريدة (unique fields). يمكن أيضًا إضافة خاصيةuniqueضمن مخطط (schema). الفهارس موضوع واسع، لذلك لن أخوض في التفاصيل هنا. ولكن على نطاق واسع، يمكن أن تساعدك حقًا في تسريع استعلاماتك كثيرًا. - حدد اسم مجموعة (
collection) بشكل صريح. على الرغم من أنMongooseيمكنه تلقائيًا إعطاء اسم مجموعة بناءً على اسم النموذج (Completedهنا، على سبيل المثال)، إلا أن هذا يعتبر تجريدًا مبالغًا فيه في رأيي. يجب أن تكون على الأقل على دراية بأسماء قواعد البيانات والمجموعات الخاصة بك في التعليمات البرمجية. - قيد القيم إذا استطعت، باستخدام التعدادات (
enums).
عمليات CRUD: إنشاء، قراءة، تحديث، وحذف البيانات
يشير مصطلح CRUD إلى (Create) إنشاء، (Read) قراءة، (Update) تحديث، و (Delete) حذف. هذه هي الخيارات الأربعة الأساسية التي يمكنك من خلالها إجراء أي نوع من معالجة البيانات في قاعدة البيانات. دعنا نرى بسرعة بعض الأمثلة على هذه العمليات.
عملية الإنشاء (Create Operation)
هذا يعني ببساطة إنشاء سجل جديد في قاعدة البيانات. دعنا نستخدم النموذج الذي حددناه أعلاه لإنشاء سجل:
try {
const res = await CompletedSchema.create(record)
} catch (error) {
console.error(error)
// handle the error
}
مرة أخرى، إليك بعض التوجيهات هنا:
- استخدم
async-awaitبدلاً من دوال رد الاتصال (callbacks) (أكثر وضوحًا، ولا يوجد فرق جوهري في الأداء). - استخدم كتل
try-catchحول الاستعلامات لأن استعلامك يمكن أن يفشل لعدد من الأسباب (سجل مكرر، قيمة غير صحيحة، وما إلى ذلك).
عملية القراءة (Read Operation)
هذا يعني قراءة القيم الموجودة من قاعدة البيانات. الأمر بسيط تمامًا كما يبدو، ولكن هناك بعض النقاط التي يجب أن تعرفها مع Mongoose:
const res = await CompletedSchema.find(info).lean()
هل ترى استدعاء الدالة lean() هناك؟ إنه مفيد جدًا للأداء. بشكل افتراضي، يقوم Mongoose بمعالجة المستند (المستندات) الذي تم إرجاعه من قاعدة البيانات ويضيف عليه أساليبه السحرية (على سبيل المثال .save).
عندما تستخدم .lean()، يقوم Mongoose بإرجاع كائنات JSON عادية بدلاً من مستندات ثقيلة على الذاكرة والموارد. مما يجعل الاستعلامات أسرع وأقل تكلفة على وحدة المعالجة المركزية (CPU) أيضًا.
ومع ذلك، يمكنك حذف .lean() إذا كنت تفكر بالفعل في تحديث البيانات (سنرى ذلك لاحقًا).
عملية التحديث (Update Operation)
إذا كان لديك بالفعل مستند Mongoose معك (دون استدعاء .lean())، يمكنك ببساطة المضي قدمًا وتعديل خاصية الكائن، وحفظه باستخدام object.save():
const doc = await CompletedSchema.findOne(info)
doc.slug = 'something-else'
await doc.save()
تذكر أنه هنا، يتم إجراء استدعاءين لقاعدة البيانات. الأول هو على findOne والثاني هو على doc.save. إذا استطعت، يجب عليك دائمًا تقليل عدد الطلبات التي تصل إلى قاعدة البيانات (لأنه إذا كنت تقارن الذاكرة والشبكة والقرص، فإن الشبكة هي الأبطأ دائمًا تقريبًا).
في الحالة الأخرى، يمكنك استخدام استعلام مثل هذا:
const res = await CompletedSchema.updateOne(<condition>, <query>).lean()
وسيتم إجراء استدعاء واحد فقط لقاعدة البيانات.
عملية الحذف (Delete Operation)
عملية الحذف مباشرة أيضًا مع Mongoose. دعنا نرى كيف يمكنك حذف مستند واحد:
const res = await CompletedSchema.deleteOne(<condition>)
تمامًا مثل updateOne، تقبل deleteOne أيضًا الوسيطة الأولى كشرط مطابقة للمستند.
هناك أيضًا طريقة أخرى تسمى deleteMany والتي يجب استخدامها فقط عندما تعلم أنك تريد حذف مستندات متعددة. في أي حالة أخرى، استخدم دائمًا deleteOne لتجنب عمليات الحذف المتعددة العرضية، خاصة عندما تحاول تنفيذ الاستعلامات بنفسك.
الخاتمة
كان هذا المقال مقدمة بسيطة لعالم Mongoose و MongoDB لمطوري Node.js. إذا استمتعت بهذا المقال، يمكنك رفع مستوى مهاراتك كمطور أكثر من خلال متابعة مسار تعلم الواجهة الخلفية في codedamn. لا تتردد في التواصل معي على Twitter لأي ملاحظات!
الخلاصة التقنية
يُبرز هذا المقال الأهمية المحورية لـ Mongoose كطبقة تجريدية لا غنى عنها فوق مشغل MongoDB الأصلي عند العمل مع Node.js. بينما توفر MongoDB مرونة كبيرة بكونها قاعدة بيانات لا تعتمد على المخططات (schemaless)، فإن Mongoose يعالج التحديات المرتبطة بهذه المرونة من خلال فرض المخططات (schemas) وتوفير أدوات قوية للتحقق من صحة البيانات وإدارة العلاقات. إن تبني أفضل الممارسات مثل استخدام .lean() لتحسين أداء القراءة، وتقليل استدعاءات قاعدة البيانات في عمليات التحديث، وتحديد الفهارس، واستخدام async-await، يضمن بناء تطبيقات واجهة خلفية قوية وفعالة وقابلة للصيانة. هذه الممارسات لا تقتصر على تحسين الأداء فحسب، بل تعزز أيضًا تجربة المطور وتقلل من احتمالية الأخطاء.