طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 242 · أطر الويب
الدرس 12 · الوحدة 4: إتقان إطار الخادم

تنظيم الخادم: Router والمتحكمات

كل ملف بمسؤولية واحدة: مسارات، ومتحكمات، وبيانات ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تقسّم خادم Express إلى مسارات ومتحكمات وبيانات.
  • تستخدم module.exports و require بين ملفات المشروع.
  • تركّب Router على بادئة بـ app.use.
  • تقرأ الإعدادات من ملف ‎.env.

١حين يكبر الملف

خادم فيه عشرون مسارًا في ملف واحد يصبح صعب القراءة والتعديل، وخصوصًا حين يعمل عليه فريق. الحل: نقسّم الكود حسب المسؤولية، تمامًا كما قسّمنا واجهة React إلى مكوّنات:

المجلدمسؤوليتهيعرف عن
server.jsإنشاء التطبيق، والوسطاء العامة، وربط المسارات، والتشغيلالمسارات فقط
routes/أي (طريقة + مسار) يذهب إلى أي دالةالمتحكمات
controllers/منطق الطلب: قراءة req، والتحقق، وإرسال الردالنماذج / البيانات
data/ أو models/التعامل مع البيانات (لاحقًا قاعدة البيانات)لا شيء من HTTP
shop-api/
├── server.js
├── routes/
│   └── products.js
├── controllers/
│   └── productController.js
└── data/
    └── products.js

هذا تطبيق مبسّط لنمط MVC: النموذج Model للبيانات، والمتحكم Controller للمنطق؛ أما «العرض» View فهو هنا ردود JSON التي تعرضها React.

٢module.exports و require

كل ملف في Node وحدة مستقلة: متغيراته لا يراها غيره، إلا ما يضعه في module.exports. والملف الآخر يستورده بـ require('./مسار') (المسار النسبي يبدأ بـ ./ أو ../، واللاحقة .js اختيارية):

التصديرالاستيراد
module.exports = router; (شيء واحد)const router = require('./routes/products');
exports.list = …; exports.show = …; (عدة أشياء)const ctrl = require('./controllers/productController'); ثم ctrl.list

٣express.Router: تطبيق مصغّر

express.Router() يصنع مجموعة مسارات مستقلة نركّبها على بادئة واحدة بـ app.use('/api/products', router). داخل ملف المسارات نكتب '/' و '/:id' فقط، والبادئة تُضاف تلقائيًّا. تنقّل بين تبويبات الملفات الأربعة:

// server.js: نقطة البداية، قصيرة وواضحةconst express = require('express');const productRoutes = require('./routes/products'); const app = express();app.use(express.json()); app.use('/api/products', productRoutes);   // كل مسارات الملف تبدأ بهذه البادئة app.use((req, res) => res.status(404).json({ error: 'المسار غير موجود' })); app.listen(3000, () => console.log('المتجر يعمل على 3000'));

سبعة طلبات تمر على الملفات الأربعة. لاحظ أن DELETE رد بـ 204 بلا محتوى، وأن الطلب الأخير لمسار غير موجود وصل إلى وسيط 404 في server.js.

٤الخطأ الأشهر: نسيان التصدير

إن نسيت module.exports = router فإن require يعيد كائنًا فارغًا {}، ويرفض Express تركيبه:

const express = require('express');const app = express();app.use('/api/users', require('./routes/users'));app.listen(3000);

الخادم لم يبدأ أصلًا. الرسالة تقول إن app.use يحتاج دالة وسيطة لكنه استلم Object. أضف السطر الناقص في المختبر.

٥الإعدادات خارج الكود: ملف ‎.env

المنفذ، وكلمة سر قاعدة البيانات، ومفاتيح التوقيع لا تُكتب داخل الكود (فهي تختلف بين جهازك والخادم الحقيقي، ولا يجوز رفعها إلى GitHub). نضعها في ملف .env، وتقرؤها حزمة dotenv إلى process.env:

require('dotenv').config();const express = require('express');const app = express(); const PORT = process.env.PORT || 3000; app.get('/api/info', (req, res) => {  res.json({ shop: process.env.SHOP_NAME, mode: process.env.NODE_ENV });}); app.listen(PORT, () => console.log('يعمل على المنفذ ' + PORT));

توقّع وتحقّق

مع app.use('/api/books', router) و router.get('/:id', …)، ما المسار الكامل؟

البادئة + المسار.

أين يُكتب منطق التحقق من البيانات وإرسال الرد؟

المسؤوليات.

ماذا يعيد require لملف لم يصدّر شيئًا؟

module.exports الافتراضي.

لماذا نضع كلمات السر في ‎.env ؟

الأمان والمرونة.

الخلاصة

تحدٍّ برمجي: تقسيم خادم المهام

متوسط

خادم مهام كُتب في ملف واحد، والمطلوب نقل مساراته إلى Router ومتحكم مع بقاء سلوكه كما هو.

المطلوب:
  • ملف routes/tasks.js فيه express.Router() ويصدّره.
  • ملف controllers/taskController.js يصدّر الدالتين list و create.
  • server.js يركّب المسارات بـ app.use('/api/tasks', …) ولا يحوي app.get أو app.post.
  • السلوك نفسه: القائمة، والإضافة بـ 201، ورفض المهمة الفارغة بـ 400.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. GET /api/tasks
  2. POST /api/tasks {"title": "كتابة الاختبارات"}
  3. POST /api/tasks {}
  4. GET /api/tasks
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • القائمة تعمل
  • الإضافة ترد بالمهمة الجديدة
  • … بالحالة 201
  • المهمة الفارغة ← 400
  • القائمة صارت مهمتين
  • routes/tasks.js يستخدم Router ويصدّره
  • المتحكم يصدّر list و create
  • server.js بلا app.get أو app.post

💡 تلميح 1
في ملف المسارات المسار '/' فقط، والبادئة في server.js.
💡 تلميح 2
مصفوفة المهام تنتقل مع المنطق إلى المتحكم.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

const express = require('express');const app = express();app.use(express.json()); app.use('/api/tasks', require('./routes/tasks')); app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس