العودة للرئيسية
الدرس الثامن

التوجيه والمسارات - Routing

تنظيم Routes بطريقة احترافية وفعالة

40 دقيقة متوسط الفصل الثالث

ما هو Router في Express؟

Express Router هو وسيلة لتنظيم Routes الخاصة بتطبيقك بشكل منظم وقابل للصيانة. بدلاً من وضع جميع Routes في ملف واحد، يمكنك تقسيمها إلى ملفات منفصلة حسب الوظيفة.

فوائد استخدام Router

  • تنظيم أفضل للكود وسهولة الصيانة
  • فصل منطق التطبيق إلى وحدات مستقلة
  • إعادة استخدام الكود بسهولة
  • تسهيل العمل الجماعي على المشروع
  • اختبار أسهل لكل جزء بشكل منفصل

إنشاء Router بسيط

لنبدأ بإنشاء Router بسيط. أنشئ مجلد جديد اسمه routes وبداخله ملف users.js:

// routes/users.js const express = require('express'); const router = express.Router(); // جلب جميع المستخدمين router.get('/', (req, res) => { res.json({ message: 'قائمة جميع المستخدمين', users: [ { id: 1, name: 'أحمد' }, { id: 2, name: 'فاطمة' } ] }); }); // جلب مستخدم معين router.get('/:id', (req, res) => { const userId = req.params.id; res.json({ message: `معلومات المستخدم ${userId}`, user: { id: userId, name: 'أحمد' } }); }); // إضافة مستخدم جديد router.post('/', (req, res) => { res.json({ message: 'تم إنشاء مستخدم جديد', user: req.body }); }); module.exports = router;

الآن في الملف الرئيسي app.js، استخدم هذا Router:

// app.js const express = require('express'); const usersRouter = require('./routes/users'); const app = express(); // Middleware لقراءة JSON app.use(express.json()); // استخدام Router app.use('/api/users', usersRouter); app.listen(3000, () => { console.log('السيرفر يعمل على المنفذ 3000'); });

الآن يمكنك الوصول إلى:

  • GET /api/users - لجلب جميع المستخدمين
  • GET /api/users/1 - لجلب مستخدم معين
  • POST /api/users - لإضافة مستخدم جديد

هيكل المشروع المنظم

لتطبيق احترافي، يجب تنظيم الملفات بشكل جيد:

my-app/ ├── app.js # الملف الرئيسي ├── package.json ├── routes/ # مجلد Routes │ ├── users.js # Routes المستخدمين │ ├── products.js # Routes المنتجات │ └── auth.js # Routes المصادقة ├── controllers/ # منطق الأعمال │ ├── userController.js │ └── productController.js ├── models/ # نماذج البيانات │ ├── User.js │ └── Product.js └── middleware/ # Middleware مخصص ├── auth.js └── validate.js

Routes متعددة منظمة

لنبني نظام Routes كامل للمنتجات:

// routes/products.js const express = require('express'); const router = express.Router(); // قاعدة بيانات مؤقتة let products = [ { id: 1, name: 'لابتوب', price: 3000 }, { id: 2, name: 'موبايل', price: 1500 } ]; // GET /api/products - جلب جميع المنتجات router.get('/', (req, res) => { res.json({ success: true, count: products.length, data: products }); }); // GET /api/products/:id - جلب منتج معين router.get('/:id', (req, res) => { const product = products.find(p => p.id === parseInt(req.params.id)); if (!product) { return res.status(404).json({ success: false, error: 'المنتج غير موجود' }); } res.json({ success: true, data: product }); }); // POST /api/products - إضافة منتج جديد router.post('/', (req, res) => { const newProduct = { id: products.length + 1, name: req.body.name, price: req.body.price }; products.push(newProduct); res.status(201).json({ success: true, data: newProduct }); }); // PUT /api/products/:id - تحديث منتج router.put('/:id', (req, res) => { const product = products.find(p => p.id === parseInt(req.params.id)); if (!product) { return res.status(404).json({ success: false, error: 'المنتج غير موجود' }); } product.name = req.body.name || product.name; product.price = req.body.price || product.price; res.json({ success: true, data: product }); }); // DELETE /api/products/:id - حذف منتج router.delete('/:id', (req, res) => { const index = products.findIndex(p => p.id === parseInt(req.params.id)); if (index === -1) { return res.status(404).json({ success: false, error: 'المنتج غير موجود' }); } products.splice(index, 1); res.json({ success: true, data: {} }); }); module.exports = router;

Router.route() للتبسيط

يمكنك استخدام router.route() لتجميع Routes التي تشترك في نفس المسار:

// بدلاً من: router.get('/products', getAllProducts); router.post('/products', createProduct); // استخدم: router.route('/products') .get(getAllProducts) .post(createProduct); // مثال كامل: router.route('/') .get((req, res) => { res.json({ message: 'جلب جميع المنتجات' }); }) .post((req, res) => { res.json({ message: 'إنشاء منتج جديد' }); }); router.route('/:id') .get((req, res) => { res.json({ message: `جلب المنتج ${req.params.id}` }); }) .put((req, res) => { res.json({ message: `تحديث المنتج ${req.params.id}` }); }) .delete((req, res) => { res.json({ message: `حذف المنتج ${req.params.id}` }); });

Router Parameters

يمكنك استخدام router.param() لتنفيذ كود معين عند وجود parameter محدد:

const express = require('express'); const router = express.Router(); // قاعدة بيانات مؤقتة const users = [ { id: 1, name: 'أحمد', email: 'ahmed@test.com' }, { id: 2, name: 'فاطمة', email: 'fatima@test.com' } ]; // Middleware للتحقق من وجود المستخدم router.param('userId', (req, res, next, id) => { const user = users.find(u => u.id === parseInt(id)); if (!user) { return res.status(404).json({ success: false, error: 'المستخدم غير موجود' }); } // حفظ المستخدم في req للاستخدام لاحقاً req.user = user; next(); }); // الآن جميع Routes التي تحتوي على :userId ستستفيد من التحقق أعلاه router.get('/:userId', (req, res) => { res.json({ success: true, data: req.user }); }); router.put('/:userId', (req, res) => { req.user.name = req.body.name || req.user.name; req.user.email = req.body.email || req.user.email; res.json({ success: true, data: req.user }); }); router.delete('/:userId', (req, res) => { const index = users.findIndex(u => u.id === req.user.id); users.splice(index, 1); res.json({ success: true, message: 'تم حذف المستخدم' }); }); module.exports = router;

مثال عملي: نظام مدونة كامل

لنبني نظام Routes كامل لمدونة:

// app.js const express = require('express'); const app = express(); // Middleware app.use(express.json()); // استيراد Routers const authRoutes = require('./routes/auth'); const postRoutes = require('./routes/posts'); const commentRoutes = require('./routes/comments'); const userRoutes = require('./routes/users'); // استخدام Routers app.use('/api/auth', authRoutes); app.use('/api/posts', postRoutes); app.use('/api/comments', commentRoutes); app.use('/api/users', userRoutes); // Route للصفحة الرئيسية app.get('/', (req, res) => { res.json({ message: 'مرحباً بك في API المدونة', endpoints: { auth: '/api/auth', posts: '/api/posts', comments: '/api/comments', users: '/api/users' } }); }); // معالجة 404 app.use((req, res) => { res.status(404).json({ success: false, error: 'المسار غير موجود' }); }); app.listen(3000, () => { console.log('API المدونة يعمل على المنفذ 3000'); });

Routes المدونة routes/posts.js:

// routes/posts.js const express = require('express'); const router = express.Router(); // قاعدة بيانات مؤقتة let posts = [ { id: 1, title: 'مقدمة في Node.js', content: 'Node.js منصة قوية...', author: 'أحمد', createdAt: new Date() } ]; // جلب جميع المقالات router.get('/', (req, res) => { // دعم الفلترة والبحث const { search, author } = req.query; let filteredPosts = posts; if (search) { filteredPosts = filteredPosts.filter(p => p.title.includes(search) || p.content.includes(search) ); } if (author) { filteredPosts = filteredPosts.filter(p => p.author === author); } res.json({ success: true, count: filteredPosts.length, data: filteredPosts }); }); // جلب مقال معين router.get('/:id', (req, res) => { const post = posts.find(p => p.id === parseInt(req.params.id)); if (!post) { return res.status(404).json({ success: false, error: 'المقال غير موجود' }); } res.json({ success: true, data: post }); }); // إنشاء مقال جديد router.post('/', (req, res) => { const { title, content, author } = req.body; // التحقق من البيانات if (!title || !content || !author) { return res.status(400).json({ success: false, error: 'يرجى إدخال جميع الحقول المطلوبة' }); } const newPost = { id: posts.length + 1, title, content, author, createdAt: new Date() }; posts.push(newPost); res.status(201).json({ success: true, data: newPost }); }); // تحديث مقال router.put('/:id', (req, res) => { const post = posts.find(p => p.id === parseInt(req.params.id)); if (!post) { return res.status(404).json({ success: false, error: 'المقال غير موجود' }); } post.title = req.body.title || post.title; post.content = req.body.content || post.content; post.updatedAt = new Date(); res.json({ success: true, data: post }); }); // حذف مقال router.delete('/:id', (req, res) => { const index = posts.findIndex(p => p.id === parseInt(req.params.id)); if (index === -1) { return res.status(404).json({ success: false, error: 'المقال غير موجود' }); } posts.splice(index, 1); res.json({ success: true, message: 'تم حذف المقال بنجاح' }); }); module.exports = router;

أفضل الممارسات

تنظيم الملفات

افصل Routes عن Controllers عن Models لسهولة الصيانة والتطوير

استخدام أسماء واضحة

اختر أسماء Routes وملفات واضحة ومعبرة عن وظيفتها

معالجة الأخطاء

تأكد من معالجة جميع الأخطاء المحتملة في كل Route

التوثيق

وثق جميع Routes وParameters المطلوبة لكل endpoint

الخطوات التالية

Middleware

تعلم كيفية استخدام Middleware لمعالجة الطلبات

المصادقة

إضافة نظام مصادقة وحماية Routes