أول خادم 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 | الاستخدام |
|---|---|---|
GET | app.get | قراءة بيانات (لا يغيّر شيئًا) |
POST | app.post | إنشاء عنصر جديد |
PUT / PATCH | app.put / app.patch | تعديل كامل / جزئي |
DELETE | app.delete | حذف |
| الرمز | المعنى | متى نرسله |
|---|---|---|
200 | OK | الافتراضي عند النجاح |
201 | Created | بعد إنشاء عنصر بـ POST |
204 | No Content | نجح الحذف ولا محتوى للرد: res.sendStatus(204) |
400 | Bad Request | بيانات الطلب ناقصة أو خاطئة |
404 | Not Found | العنصر غير موجود |
500 | Internal Server Error | خطأ في كود الخادم |
٧مختبرك: جرّب بنفسك
هذا خادم مكتمل لقائمة مهام. أرسل إليه طلبات مختلفة من تبويب «📮 طلب يدوي»: أضف مهمة بـ POST، ثم اطلب القائمة بـ GET. وجرّب إضافة مسار DELETE /api/todos/:id بنفسك.
توقّع وتحقّق
ما وظيفة app.listen(3000)؟
Port.أين تصل قيمة :id في المسار /api/books/:id ؟
ما الرمز المناسب بعد إنشاء عنصر جديد بنجاح؟
Created.ما الذي يجعل req.body كائنًا عند إرسال JSON؟
express.json.الخلاصة
Expressيربط (طريقة + مسار) بدالة معالِجة(req, res).res.sendنص أوHTML، وres.jsonبيانات، وres.status(n)قبلهما.req.paramsمن المسار، وreq.queryبعد ؟، وreq.bodyيحتاجexpress.json().- 201 للإنشاء، و400 للبيانات الخاطئة، و404 لغير الموجود، و500 لخطأ الخادم.
returnبعد الرد المبكر حتى لا يُرسل ردّان.
تحدٍّ برمجي: واجهة برمجة الطلاب
متوسطخادم صغير لقائمة طلاب في الذاكرة، تستهلكه واجهة React لاحقًا.
GET /api/studentsيرد بالمصفوفة كاملة بصيغة JSON.GET /api/students/:idيرد بالطالب، أو بالحالة 404 و{"error": "غير موجود"}.POST /api/studentsيضيف طالبًا منnameفي الجسم، بمعرّف تالٍ، ويرد بالحالة 201 والطالب الجديد.- إن غاب
nameيرد بالحالة 400.
👀 اعرض الاستجابات المطلوبة
GET /api/studentsGET /api/students/2GET /api/students/7POST /api/students{"name": "ريم"}POST /api/students{}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);