الـ middleware في Express دالة بالتوقيع (req, res, next) تعمل بين لحظة وصول الطلب ولحظة إرسال الاستجابة. يمكن لكل middleware أن يقرأ req وres أو يغيّرهما، أو ينهي الطلب بإرسال استجابة، أو يستدعي next() ليسلّم الطلب إلى الدالة التالية في الصف. يضيف app.use(fn) middleware لكل طلب، ويشغّل Express كل شيء بالترتيب الذي سجّلته به.
تسجّل وحدة التحكم الخطوات الثلاث بالترتيب مع كل طلب، ويقرأ المسار req.startedAt، وهي خاصية أضافها المسجّل. أرسل GET /missing: يسجّل الـ middleware الاثنان مع ذلك، لأن app.use() يطابق كل مسار، ثم يجيب Express بـ 404 لأنه لم يطابق أي مسار.
نسيان next()
الـ middleware الذي لا يرسل استجابة ولا يستدعي next() يترك الطلب معلّقًا. ينتظر العميل حتى تنقضي المهلة، ولا يظهر شيء في السجلات، وهذا يجعله من أخطاء الـ middleware الشائعة. احذف next(); من logger في المثال السابق وأرسل GET / مرة أخرى: لن يحصل الطلب على إجابة أبدًا.
القاعدة بسيطة: كل مسار تنفيذ داخل الـ middleware ينتهي بواحد بالضبط من هذه:
next()للمتابعة،next(err)للقفز إلى معالج الأخطاء (راجع معالجة الأخطاء في Express)،- استجابة مثل
res.json()أوres.status(401).send().
استدعاء next() بعد إرسال استجابة خطأ أيضًا. يحاول المعالج التالي إرسال استجابة ثانية فيرمي Express الخطأ ERR_HTTP_HEADERS_SENT. اكتب return res.status(401).json(...) لتتوقف الدالة عندها.
فحص مصادقة يوقف الطلب
الـ middleware الذي يرفض طلبًا يرسل الاستجابة بنفسه ولا يستدعي next() أبدًا. مرّره كوسيط إضافي إلى مسار لتحمي ذلك المسار وحده:
يحصل الطلب الأول على 401 مع {"error":"Missing or wrong API key"}. أضف الترويسة x-api-key: secret123 في شريط الطلب وأرسله مرة أخرى: الإجابة الآن {"users":42,"caller":"reporting-app"}. ولا يشغّل /public الفحص أبدًا.
أين يعمل الـ middleware
| ما تكتبه | يعمل مع |
|---|---|
app.use(fn) | كل طلب، وكل طريقة |
app.use('/api', fn) | المسارات التي تبدأ بـ /api |
app.get('/admin', fn, handler) | طلب GET إلى /admin فقط، قبل handler |
router.use(fn) | كل مسار في ذلك الموجّه |
الترتيب جزء من المعنى. المسجّل المسجَّل بعد المسارات لا يسجّل أبدًا الطلبات التي أجابتها تلك المسارات، لأن المسار الذي يرسل استجابة لا يستدعي next(). سجّل الـ middleware العام للتطبيق، مثل التسجيل وتحليل الجسم وCORS، أولًا، ثم المسارات، ثم معالجي 404 والأخطاء.
الـ middleware المدمج
يأتي Express مع بعض دوال الـ middleware التي تضيفها بـ app.use(). محللات الجسم تملأ req.body؛ ومن دونها يكون undefined.
| الـ middleware | ماذا يفعل |
|---|---|
express.json() | يحلل أجسام JSON (Content-Type: application/json) |
express.urlencoded() | يحلل إرسالات نماذج HTML (name=Ada&age=36) |
express.text() | يضع جسمًا نصيًا عاديًا في req.body كنص |
express.static('public') | يقدّم الملفات من مجلد |
أرسل POST /echo مع الجسم {"name": "Ada"} فتتضمن الإجابة "body":{"name":"Ada"}. احذف سطر express.json() وأرسله مرة أخرى، فيختفي body من الإجابة: req.body هو undefined، وJSON.stringify يحذف القيم غير المعرّفة. الحزم مثل cors وhelmet وmorgan هي middleware أيضًا: تثبّتها بـ npm install وتمررها إلى app.use() بالطريقة نفسها. تغطي صفحة express.static تقديم الملفات.
أخطاء شائعة
استدعاء الـ middleware بدلًا من تمريره. يشغّل app.use(logger()) الدالة logger مرة واحدة عند بدء التشغيل ويسجّل القيمة التي تعيدها. اكتب app.use(logger). (لـ express.json() أقواس لأنها تعيد دالة middleware.)
إضافة محلل الجسم بعد المسارات. تعمل المسارات أولًا فترى req.body على أنه undefined.
توقّع أن app.use('/admin', fn) يطابق مسارًا واحدًا. يعمل أيضًا مع /admin/users وكل مسار آخر تحت /admin، ومع كل طريقة. لحماية مسار واحد، مرّر الـ middleware إلى app.get('/admin', fn, handler).
الأسئلة الشائعة
ما الفرق بين middleware ومعالج المسار؟
تقنيًا لا فرق: كلاهما دالة بالشكل (req, res, next). بحسب العُرف، ينهي معالج المسار الطلب باستجابة، بينما يؤدي الـ middleware عادة عملًا مشتركًا ويستدعي next().
هل يمكن أن يكون الـ middleware غير متزامن؟
نعم. في Express 5 يرسل الـ middleware غير المتزامن الذي يرمي خطأ أو يعيد وعدًا مرفوضًا الخطأ إلى معالج الأخطاء تلقائيًا. انتظر بـ await ما تحتاجه، ثم استدعِ next().
ماذا تفعل next('route')؟
تتخطى بقية معالجات المسار الحالي وتنتقل إلى المسار المطابق التالي. لا تعمل إلا في المعالجات المضافة عبر app.get() وapp.post() ودوال الطرق الأخرى، لا في app.use().
كيف أتخطى middleware لمسار واحد؟
سجّل ذلك المسار قبل الـ middleware ليجيب أولًا، أو افحص المسار داخل الـ middleware (if (req.path === '/health') return next();). وتطبيق الـ middleware على موجّه بعينه أو مسار بعينه بدلًا من التطبيق كله يلغي الحاجة إلى ذلك غالبًا.