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

أول خادم Express

Node.js و Express: مسارات، وطلبات، وردود JSON ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح دور الخادم و Node.js و Express في تطبيق الويب.
  • تنشئ خادمًا بمسارات GET و POST وتشغّله على منفذ.
  • تقرأ معاملات المسار والاستعلام وجسم الطلب.
  • ترسل JSON مع رمز الحالة المناسب.

١الخادم: النصف الآخر من التطبيق

تطبيقات React التي بنيناها تعمل في متصفح المستخدم. لكن البيانات المشتركة (حسابات المستخدمين، والطلبات، والمنتجات) يجب أن تُحفظ في مكان واحد يصل إليه الجميع: الخادم. الواجهة ترسل طلب HTTP، والخادم يرد باستجابة، غالبًا بصيغة JSON.

Node.js بيئة تشغّل JavaScript خارج المتصفح، وExpress أشهر إطار خوادم فوقها: صغير، ويعطيك طريقة واضحة لتقول «إذا جاء طلب GET إلى /api/books فنفّذ هذه الدالة».

الواجهة (React)الخادم (Express)
أين يعمل؟متصفح كل مستخدمجهاز واحد (خادم) يخدم الجميع
يتعامل معالأزرار، والنماذج، والعرضالطلبات، وقاعدة البيانات، والصلاحيات
يتواصلان عبرfetch(...) يرسل الطلبres.json(...) يرسل الرد

إنشاء مشروع خادم على جهازك يحتاج ثلاثة أوامر في الطرفية:

🖥 الطرفية على جهازك
$ mkdir library-api && cd library-api$ npm init -y$ npm install expressadded 66 packages in 3s$ node server.jsالخادم يعمل على http://localhost:3000

٢أول خادم

أربعة أسطر جوهرية: نستورد Express، ونصنع التطبيق app، ونعرّف مسارًا Route، ثم نبدأ الاستماع على منفذ:

const express = require('express');const app = express(); app.get('/', (req, res) => {  res.send('<h1>مرحبًا من Express 👋</h1>');}); app.listen(3000, () => {  console.log('الخادم يعمل على http://localhost:3000');});

الطلب GET / وصل، فنُفّذت الدالة، وأرسلت res.send نص HTML. لاحظ في الطرفية ما طبعه console.log عند بدء التشغيل، وفي تبويب 📨 HTTP الترويسات التي أضافها Express تلقائيًّا مثل Content-Type.

الجزءمعناه
app.get('/', handler)إذا جاء طلب بالطريقة GET إلى المسار / فنفّذ handler
(req, res) => {…}المعالِج: req يصف الطلب الوارد، و res أداة إرسال الرد
res.send('…')يرسل الرد وينهي الطلب (نص، أو HTML)
app.listen(3000, cb)ابدأ الاستماع على المنفذ 3000، ثم نفّذ cb

وإن طلب المتصفح مسارًا لم نعرّفه، يرد Express وحده بـ 404:

const express = require('express');const app = express(); app.get('/', (req, res) => {  res.send('<h1>مرحبًا من Express 👋</h1>');}); app.listen(3000, () => {  console.log('الخادم يعمل على http://localhost:3000');});

لا يوجد مسار /about، فرد Express بالحالة 404 ونص «Cannot GET /about».

٣JSON: لغة الحوار مع React

الواجهات الحديثة لا تطلب من الخادم صفحات HTML، بل بيانات. res.json(قيمة) يحوّل المصفوفة أو الكائن إلى نص JSON ويضبط الترويسة Content-Type: application/json:

const express = require('express');const app = express();app.use(express.json()); let books = [  { id: 1, title: 'تعلم JavaScript', author: 'سارة', year: 2021 },  { id: 2, title: 'أساسيات الويب', author: 'خالد', year: 2023 },]; app.get('/api/books', (req, res) => {  res.json(books);}); app.listen(3000);

٤معاملات المسار والاستعلام

كيف نطلب كتابًا واحدًا؟ نضع في المسار معاملًا يبدأ بنقطتين :id؛ قيمته تصل في req.params.id نصًّا دائمًا. أما ما بعد علامة ؟ في الرابط فيصل في req.query:

const express = require('express');const app = express();app.use(express.json()); let books = [  { id: 1, title: 'تعلم JavaScript', author: 'سارة', year: 2021 },  { id: 2, title: 'أساسيات الويب', author: 'خالد', year: 2023 },]; app.get('/api/books', (req, res) => {  res.json(books);}); app.get('/api/books/:id', (req, res) => {  const id = Number(req.params.id);          // '2' ← 2  const book = books.find(b => b.id === id);  if (!book) {    return res.status(404).json({ error: 'الكتاب غير موجود' });  }  res.json(book);}); // /api/search?author=سارةapp.get('/api/search', (req, res) => {  const { author } = req.query;  res.json(books.filter(b => b.author === author));}); app.listen(3000);

الطلب الثاني لكتاب غير موجود: ضبطنا الحالة 404 بـ res.status(404) قبل الإرسال. وreturn ضرورية: بدونها يكمل الكود ويحاول إرسال ردّ ثانٍ.

الرابطالتعريفالقيمة
/api/books/2'/api/books/:id'req.params.id === '2'
/api/search?author=سارة&year=2021'/api/search'req.query → { author: 'سارة', year: '2021' }

٥استقبال البيانات: POST وجسم الطلب

لإضافة كتاب ترسل الواجهة طلب POST ومعه جسم Body بصيغة JSON. Express لا يقرأ الجسم تلقائيًّا؛ السطر app.use(express.json()) هو الذي يحوّله إلى كائن في req.body:

const express = require('express');const app = express();app.use(express.json()); let books = [  { id: 1, title: 'تعلم JavaScript', author: 'سارة', year: 2021 },  { id: 2, title: 'أساسيات الويب', author: 'خالد', year: 2023 },]; app.get('/api/books', (req, res) => {  res.json(books);}); app.post('/api/books', (req, res) => {  const { title, author } = req.body;  if (!title || !author) {    return res.status(400).json({ error: 'العنوان والمؤلف مطلوبان' });  }  const book = { id: books.length + 1, title, author, year: 2025 };  books.push(book);  res.status(201).json(book);        // 201 Created: أُنشئ مورد جديد}); app.listen(3000);

الطلب الأول أضاف الكتاب (201)، والثاني رُفض لنقص البيانات (400)، والثالث يُظهر أن الكتاب الجديد صار في المصفوفة. البيانات هنا في ذاكرة الخادم: تختفي إن أُعيد تشغيله (في الوحدة 5 نحفظها في قاعدة بيانات).

وماذا لو نسينا express.json()؟ يصل req.body فارغًا undefined وينهار المعالج:

const express = require('express');const app = express();// نسينا: app.use(express.json()); app.post('/api/books', (req, res) => {  const { title } = req.body;  res.json({ title });}); app.listen(3000);

الحالة 500 تعني خطأ داخل الخادم. الطرفية تُظهر السبب: لا يمكن استخراج title من undefined. أضف السطر الناقص في المختبر وأعد الإرسال.

٦الطرق ورموز الحالة

كل مسار يرتبط بطريقة HTTP تعبّر عن نية الطلب، والرد يحمل رمز حالة يخبر الواجهة بالنتيجة دون أن تقرأ النص:

الطريقةفي Expressالاستخدام
GETapp.getقراءة بيانات (لا يغيّر شيئًا)
POSTapp.postإنشاء عنصر جديد
PUT / PATCHapp.put / app.patchتعديل كامل / جزئي
DELETEapp.deleteحذف
الرمزالمعنىمتى نرسله
200OKالافتراضي عند النجاح
201Createdبعد إنشاء عنصر بـ POST
204No Contentنجح الحذف ولا محتوى للرد: res.sendStatus(204)
400Bad Requestبيانات الطلب ناقصة أو خاطئة
404Not Foundالعنصر غير موجود
500Internal Server Errorخطأ في كود الخادم

٧مختبرك: جرّب بنفسك

هذا خادم مكتمل لقائمة مهام. أرسل إليه طلبات مختلفة من تبويب «📮 طلب يدوي»: أضف مهمة بـ POST، ثم اطلب القائمة بـ GET. وجرّب إضافة مسار DELETE /api/todos/:id بنفسك.

🧪 مختبر Expressعدّل الملفات، ثم أرسل طلبًا يدويًّا أو افتح الرابط في المتصفح المصغّر. الخادم يعمل داخل صفحتك.

توقّع وتحقّق

ما وظيفة app.listen(3000)؟

المنفذ Port.

أين تصل قيمة :id في المسار /api/books/:id ؟

معاملات المسار.

ما الرمز المناسب بعد إنشاء عنصر جديد بنجاح؟

Created.

ما الذي يجعل req.body كائنًا عند إرسال JSON؟

الوسيط express.json.

الخلاصة

تحدٍّ برمجي: واجهة برمجة الطلاب

متوسط

خادم صغير لقائمة طلاب في الذاكرة، تستهلكه واجهة React لاحقًا.

المطلوب:
  • GET /api/students يرد بالمصفوفة كاملة بصيغة JSON.
  • GET /api/students/:id يرد بالطالب، أو بالحالة 404 و {"error": "غير موجود"}.
  • POST /api/students يضيف طالبًا من name في الجسم، بمعرّف تالٍ، ويرد بالحالة 201 والطالب الجديد.
  • إن غاب name يرد بالحالة 400.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. GET /api/students
  2. GET /api/students/2
  3. GET /api/students/7
  4. POST /api/students {"name": "ريم"}
  5. POST /api/students {}
  6. GET /api/students
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • GET /api/students يرد بالمصفوفة
  • GET /api/students/2 يرد بفهد
  • طالب غير موجود ← 404
  • … ومعه {"error": "غير موجود"}
  • POST يرد بـ 201
  • … والطالب الجديد بمعرّف 3
  • POST بلا اسم ← 400
  • الطالب الجديد أصبح في القائمة (3 طلاب)

💡 تلميح 1
لا تنس app.use(express.json()) قبل المسارات.
💡 تلميح 2
حوّل المعرّف: Number(req.params.id)، واستخدم return res.status(404).json(…).

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

const express = require('express');const app = express();app.use(express.json()); let students = [  { id: 1, name: 'سارة' },  { id: 2, name: 'فهد' },]; app.get('/api/students', (req, res) => {  res.json(students);}); app.get('/api/students/:id', (req, res) => {  const s = students.find(x => x.id === Number(req.params.id));  if (!s) return res.status(404).json({ error: 'غير موجود' });  res.json(s);}); app.post('/api/students', (req, res) => {  if (!req.body.name) return res.status(400).json({ error: 'الاسم مطلوب' });  const s = { id: students.length + 1, name: req.body.name };  students.push(s);  res.status(201).json(s);}); app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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