تنشئ http.createServer() خادم ويب من دالة واحدة، هي معالج الطلبات، يستدعيها Node مع كل طلب وارد مع كائنين: req (الطريقة وعنوان URL والترويسات التي أرسلها العميل) وres (ما ترسله في المقابل). اضبط رمز الحالة والترويسات على res، ثم استدعِ res.end() مع الجسم. ويبدأ server.listen(3000) بقبول الاتصالات على المنفذ 3000.
يُجاب الطلب الأول بـ You asked for GET /hello?name=Ada، وتسجّل وحدة التحكم الطريقة وعنوان URL نفسيهما. أرسل DELETE /anything من شريط الطلب: يجيبه المعالج نفسه، لأن خادم http العادي ليست فيه مسارات. كل طلب يمر بالدالة الوحيدة.
إرسال JSON وضبط رمز الحالة
تضبط res.writeHead(status, headers) رمز الحالة والترويسات في استدعاء واحد. لإرسال JSON، اضبط Content-Type: application/json وأرسل نتيجة JSON.stringify()؛ فـ res.end() لا تقبل إلا نصًا أو Buffer.
يعيد GET /users?limit=1 القيمة [{"id":1,"name":"Ada"}]. req.url هو المسار مع سلسلة الاستعلام، فيقسمه new URL(req.url, 'http://localhost') إلى pathname وsearchParams (العنوان الأساسي موجود فقط لأن URL يحتاج عنوانًا مطلقًا). أرسل GET /nothing فتصبح الحالة 404.
توجيه الطلبات عبر switch
لا تحتاج واجهة API صغيرة إلى أكثر من switch على الطريقة والمسار. ابنِ مفتاحًا مثل 'GET /users' وأعطِ كل مسار case خاصًا به:
أرسل POST /users مع الجسم {"name": "Grace"}: الإجابة 201 مع {"id":2,"name":"Grace"}، ويسرد GET /users الآن المستخدمَين كليهما. أرسل طلب POST نفسه مع الجسم hello فتحصل على 400 مع {"error":"Body must be JSON"}.
قراءة جسم طلب POST
لا يحلل Node الجسم نيابة عنك. req تدفق قابل للقراءة، ويصل الجسم على أجزاء عبر أحداث data، ثم ينطلق end حين يكتمل. تجمع readBody() في المثال السابق الأجزاء في نص وتعيد وعدًا، فيستطيع المعالج انتظاره بـ await. وبما أن req قابل للمرور عليه بشكل غير متزامن، فهذه الحلقة تؤدي العمل نفسه:
let data = '';
for await (const chunk of req) data += chunk;
const body = JSON.parse(data);
الخادم الحقيقي يحدّ أيضًا من الحجم (يتوقف عن القراءة ويجيب بـ 413 حين يتجاوز data.length حدًا ما) ويفحص req.headers['content-type'] قبل استدعاء JSON.parse.
أخطاء شائعة
نسيان res.end(). الفرع الذي يضبط الحالة ولا ينهي الاستجابة أبدًا يترك العميل ينتظر حتى تنقضي المهلة.
ضبط ترويسة بعد الجسم. بعد أن تعمل res.end() أو أول res.write()، تكون الترويسات قد أُرسلت، فترمي res.setHeader() الخطأ ERR_HTTP_HEADERS_SENT. استخدم return حين ترسل استجابة مبكرًا، كما تفعل الأمثلة.
مقارنة req.url بمسار. يتضمن req.url سلسلة الاستعلام، فتكون req.url === '/users' خاطئة مع /users?limit=1. قارن pathname.
متى تستخدم Express بدلًا من ذلك
تكفي وحدة http لفحص صحة الخادم، أو لاستقبال webhook، أو لتعلّم طريقة عمل HTTP. لكن حين تحتاج إلى معاملات في المسار مثل /users/:id، وتحليل أجسام JSON، وmiddleware، ومعالجة الأخطاء، ينتهي بك الأمر إلى كتابة إطار عمل صغير خاص بك. Express هو ذلك الإطار، مبني على كائنَي req وres نفسيهما؛ وتعرض صفحة التوجيه في Express الواجهة نفسها مع app.get() وapp.post().
الأسئلة الشائعة
ما الفرق بين res.write وres.end؟
ترسل res.write() جزءًا من الجسم وتُبقي الاستجابة مفتوحة، فيمكنك استدعاؤها مرات كثيرة. وترسل res.end() جزءًا أخيرًا اختياريًا وتنهي الاستجابة. كل استجابة تحتاج إلى res.end() واحدة بالضبط.
كيف أوقف خادم HTTP في Node؟
استدعِ server.close(). يتوقف فورًا عن قبول اتصالات جديدة ويشغّل الـ callback الخاص به حين تنتهي الاتصالات المفتوحة، وهذا ما يحتاجه الإيقاف النظيف عند SIGTERM.
كيف أستخدم HTTPS مع وحدة http؟
استخدم وحدة https بدلًا منها: تأخذ https.createServer({ key, cert }, handler) المعالج نفسه مع مفتاح خاص وشهادة. في الإنتاج يتولى وكيل مثل Nginx أو منصة الاستضافة إنهاء TLS عادة، ويبقى خادم Node لديك يستمع عبر HTTP العادي.
على أي منفذ يجب أن يستمع خادم Node؟
أي منفذ حر فوق 1024 يعمل دون صلاحيات المسؤول؛ والمنفذان 3000 و8080 شائعان في التطوير. تخبرك منصات الاستضافة عادة بالمنفذ في متغير بيئة، فاكتب server.listen(process.env.PORT || 3000).