1. أول شهر في الـ Production — لحظة الصفر
بص، أنا مش هكلمك نظري.
أول مشروع حقيقي مسكته — API بيكلم تطبيق موبايل — كان عبارة عن 5 Endpoints. كل حاجة بسيطة، الكود نضيف، وأنا حاسس إني عبقري زمني. الدنيا ماشية زي الفل.
لحد ما المدير جه في الستاند أب وقال جملة غيرت كل حاجة:
“بقولك إيه، احنا محتاجين نتأكد إن أي ريكويست جاي من يوزر عامل Login.”
قشطة. دخلت حطيت كود الـ Auth في الـ 5 Endpoints. سهلة.
بعدها بأسبوع:
“عايزين نضيف Logging عشان نتابع الـ Requests بتيجي منين.”
ضفت سطرين في كل Endpoint. بقوا 10 سطور بيتعادوا في كل مكان. بس لسه الدنيا ماشية.
بعد شهر، المشروع كبر — 20 Endpoint. والمدير دخل قال:
“السيستم بطيء شوية. ضيف تايمنج على كل ريكويست عشان نشوف الـ Bottleneck فين.”
فتحت أول Endpoint عشان أزود الحسبة دي.. ولقيت نفسي بصيت على 28 سطر بيعملوا نفس الحاجة بالظبط — متكررين في كل Endpoint — قبل ما أبدأ أكتب الشغل الفعلي بتاع الـ Endpoint أصلاً!
مسكت دماغي وقلت: “ده مش كود.. ده عك منظم.”
وده كان أول يوم أفهم فيه يعني إيه Middleware.
💡 الحقيقة المؤلمة: لو إنت شغّال Backend من غير ما تفهم Middleware — فأنت مش بتكتب كود، أنت بتعمل Copy Paste وبتسميه شغل. والفرق بين المطور اللي بيفهمها والمطور اللي مش فاهمها هو الفرق بين واحد بيبني بيت بمخطط هندسي وواحد بيركب طوب على بعضه ويدعي ربنا ميقعش.
2. المشكلة: الكود المتكرر ده بيموتك بالبطيء
خليني أوريك الكود اللي كنت بكتبه. فاكر الـ handleRequest بتاع اليوم التالت؟
// ❌ الكود قبل ما أفهم الـ Middleware — مكرونة اسباجيتي
function handleRequest(req, res) {
if (req.url === '/api/users' && req.method === 'GET') {
// ① Auth: نفس الكود في كل حتة
const token = req.headers['authorization']?.split(' ')[1];
if (!token) { res.end(JSON.stringify({ error: 'Unauthorized' })); return; }
let user;
try { user = verifyToken(token); }
catch { res.end(JSON.stringify({ error: 'Invalid token' })); return; }
req.user = user;
// ② Logging: نفس الكود بيتعاد تاني
console.log(`[${new Date().toISOString()}] GET /api/users`);
const start = Date.now();
// ③ Rate Limiting: نفس الكود تالت مرة
const count = rateCounts.get(req.ip) || 0;
if (count >= 100) { res.end(JSON.stringify({ error: 'Too many requests' })); return; }
rateCounts.set(req.ip, count + 1);
// ④ الشغل الحقيقي بقى — اللي المفروض هو بس اللي يتكتب هنا:
const users = db.getAll('users');
res.end(JSON.stringify({ data: users }));
console.log(`← 200 [${Date.now() - start}ms]`);
}
// ... وتخيل بقى نفس الخطوات ① و ② و ③ في كل endpoint تانية
if (req.url === '/api/orders' && req.method === 'POST') {
// نفس الـ Auth — copy paste
// نفس الـ Logging — copy paste
// نفس الـ Rate Limiting — copy paste
// الشغل الحقيقي بتاع الـ Orders
}
}
إيه الكوارث المعمارية اللي هنا؟
| المشكلة | التأثير الحقيقي في الـ Production |
|---|---|
| كل Endpoint فيها 20+ سطر Boilerplate | 80% من الكود بتاعك مش Business Logic — ده ضوضاء بتخفي المنطق الحقيقي ورا جبل من التكرار |
| نسيت Endpoint واحدة من الـ Auth | مبروك، عملت ثغرة أمنية (Security Vulnerability) — وده بيحصل في شركات كبيرة فعلاً |
| عايز تغير الـ Rate Limit من 100 لـ 200 | هتلف على 20 مكان وتعدل — ولو نسيت مكان واحد هتبقى عامل Inconsistent Behavior |
| الـ Onboarding لمطور جديد | هيقعد أسبوع يفهم إيه اللي بيحصل في كل Endpoint لأن كل واحدة فيها حيطة كود |
| الـ Testing | مستحيل تعمل Unit Test للـ Auth لوحده لأنه ملزوق في كل Endpoint |
ده اللي بنسميه Spaghetti Code. ومش بس قبيح — ده خطير. لأن كل تكرار هو فرصة لـ Bug جديد.
والحل؟ الـ Middleware Pipeline — الفكرة البسيطة اللي غيرت شكل الـ Backend للأبد.
3. الفكرة المطرقعة: الـ Request عبارة عن قطر بيعدي على محطات
بدل ما تتعامل مع الـ Request على إنها بتخبط في مكان واحد مفروض يعمل كل حاجة، تخيل إنها قطر بيعدي على محطات:
🚂 Request (القطر)
│
▼
🚉 محطة 1: Logger ← "سجلت بياناتك يا باشا، اتفضل كمل"
│
▼
🚉 محطة 2: Rate Limit ← "لسه متخطيتش الليميت، عدي"
│
▼
🚉 محطة 3: Auth Guard ← "التوكن بتاعك سليم، حطيت بياناتك في الأوردر وعديتك"
│
▼
🚉 محطة 4: Body Parser ← "قريت الـ JSON وحولته لـ Object، عدي"
│
▼
🏁 Controller ← هنا بقى نكتب الشغل الحقيقي — Business Logic فقط
│
▼
📦 Response ← بنرد على العميل
كل محطة بتعمل حاجة واحدة بس — وبعدين بتقول “اتفضل” للمحطة اللي بعدها. ولو أي محطة لاقت مشكلة، بتوقف القطر فوراً وترد هي على العميل — المحطات اللي بعدها مش بتشتغل أصلاً.
🧠 خد بالك من الـ Design Principle هنا: كل محطة بتطبق مبدأ الـ Single Responsibility Principle (SRP) — كل وحدة ليها مسؤولية واحدة بس. وده مش مجرد كلام نظري — ده اللي بيخلي الكود بتاعك Testable و Maintainable و Reusable. تقدر تختبر الـ Auth لوحده، والـ Rate Limiter لوحده، وتستخدمهم في أي مشروع تاني.
ده ببساطة الـ Middleware Pipeline — وهو ده اللي شغال تحت الغطا في Express و Fastify و Koa وأي Framework محترم.
4. إزاي بتتكتب: دالة بتاخد (req, res, next)
الـ Middleware مش سحر أسود ولا حاجة. دي مجرد Function عادية جداً — بس بتاخد باراميتر تالت اسمه next:
function anyMiddleware(req, res, next) {
// ① نفذ المهمة بتاعتك (قراءة، تشييك، تعديل، أو حتى إضافة بيانات)
// ② وبعدين قدامك طريقين — ومفيش تالت:
next(); // "باشا أنا خلصت، كمل للي بعدي"
// أو:
res.end(...); // "ستوب هنا — اقفل الخط ورد على العميل"
}
الـ req: الريكويست. تقدر تقرأ منه (الـ Headers، الـ URL، الـ Method)، أو تلزق فيه حاجة (زي req.user بعد ما تفك التوكن). الـ res: الريسبونس. لو رديت منه، القطر بيقف — الباب اتقفل على كل اللي بعدك. الـ next: دي مفتاح اللعبة كلها. أول ما بتندهلها كأنك بتقول “أنا تمام، الدور على المحطة الجاية”.
⚠️ قاعدة ذهبية — احفظها: في أي Middleware لازم في الآخر تعمل حاجة من الاتنين — يا تنده
next()يا ترد بـres. لو معملتش ولا واحدة فيهم، الريكويست هتفضل متعلقة للأبد (Hanging Request) والعميل هيفضل يلف ومش هيجيله حاجة. ودي أكتر غلطة المبتدئين بيقعوا فيها — وبتعمل Memory Leak في الـ Production لأن الـ Connections بتتراكم ومش بتتقفل.
5. تعال نبني الـ Pipeline بإيدينا — تفاصيل الـ next من جوه
عشان تبقى فاهم Express بيعمل إيه وراء الكواليس، تعال نكتب إحنا الـ Pipeline. وده مش تمرين نظري — ده تقريباً نفس الكود اللي Express كاتباه:
const middlewares = [logger, rateLimiter, requireAuth, parseBody];
// ↑ ↑ ↑ ↑
// محطة 1 محطة 2 محطة 3 محطة 4
function runPipeline(req, res, controller) {
let index = 0; // هنبدأ من أول محطة
function next(err) {
// الحالة ①: لو جالك Error، انطرد علطول للـ Error Handler
if (err) {
return globalErrorHandler(err, req, res);
}
// الحالة ②: هات الـ Middleware اللي عليها الدور وشغلها
const fn = middlewares[index++]; // كل ما بننده next، الـ index بيزيد واحد
if (fn) {
try {
fn(req, res, next); // ← خد بالك: بنديها نفس الـ next عشان تندهها هي كمان
} catch (syncErr) {
// ← لو الـ Middleware عملت throw بدل ما تنده next(err)
globalErrorHandler(syncErr, req, res);
}
} else {
// الحالة ③: خلصنا كل الـ Middlewares؟ شغل الـ Controller
controller(req, res);
}
}
next(); // 🎯 اضرب صفارة البداية — أول نداء بيحرك العجلة كلها
}
السكة بتتمشي إزاي بالظبط؟
runPipeline(req, res, usersController)
│
▼
next() → index=0 → logger(req, res, next)
│
next() → index=1 → rateLimiter(req, res, next)
│
next() → index=2 → requireAuth(req, res, next)
│
next() → index=3 → parseBody(req, res, next)
│
next() → index=4 → undefined
│
usersController(req, res) 🏁
نداء واحد بس لـ next بيحرك السلسلة دي كلها. كل دالة بتخلص شغلها وتنده next اللي بتنده الدالة اللي وراها — Recursive Chain من تحت لتحت.
🔍 لحظة “يااااه” — الكود الحقيقي بتاع Express: لو فتحت الـ Source Code بتاع Express على GitHub — ملف
router/index.js— هتلاقي function اسمهاnext()بتعمل نفس الحاجة بالظبط: بتمشي على Array من الـ Layers (كل Layer = Middleware) وبتندهها واحدة ورا التانية. Express مش سحر — هي بالظبط الكود اللي إحنا كتبناه فوق، بس مع شوية Error Handling و Pattern Matching زيادة.
6. بناء الـ Pipeline بنمط الـ Onion Model — الطبقات اللي بتلف الريكويست والريسبونس
في الحقيقة، الـ Pipeline مش مجرد خط مستقيم. هي أشبه بـ بصلة (Onion) — كل Middleware بتلف الريكويست وقت الدخول، وبتقدر تلفه كمان وقت الخروج.
┌─────────────────────────────────────────────┐
│ Logger Middleware │
│ ┌─────────────────────────────────────┐ │
│ │ Rate Limiter Middleware │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Auth Middleware │ │ │
│ │ │ ┌─────────────────────┐ │ │ │
│ │ │ │ Body Parser │ │ │ │
│ │ │ │ ┌─────────────┐ │ │ │ │
REQUEST ─────────► │ │ │ │ Controller │ │ │ │ ──────► RESPONSE
│ │ │ │ └─────────────┘ │ │ │ │
│ │ │ └─────────────────────┘ │ │ │
│ │ └─────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
إيه الجديد هنا؟ إن الـ Logger مثلاً تقدر تسجل الريكويست وقت ما يدخل، وكمان تسجل الريسبونس وقت ما يطلع — لأنها لفت الـ Controller من بره. وده بالظبط اللي بنعمله لما بنمسك res.end ونعمله Override:
function logger(req, res, next) {
const start = Date.now();
console.log(`→ ${req.method} ${req.url}`);
// ← بنصطاد الريسبونس وهو طالع
const originalEnd = res.end;
res.end = function (...args) {
console.log(`← ${res.statusCode} [${Date.now() - start}ms]`);
originalEnd.apply(res, args);
};
next(); // ← الريكويست بيدخل جوه البصلة
// ← لما الـ Controller ينده res.end، الكود بتاعنا فوق هو اللي هيشتغل الأول
}
🧅 الـ Onion Model ده مش مجرد تشبيه — ده Pattern حقيقي بيُستخدم في Koa.js بشكل صريح، وفي Express بشكل ضمني. لما تفهمه، هتفهم إزاي الـ Logging Middleware بتقيس الوقت الكلي للريكويست مش بس وقت الدخول.
7. الـ Middlewares بتفضح مستوى الديفيلوبر الحقيقي
تعال نكتب دالة Logging بتلات مستويات عشان تشوف الفرق بين التلميذ والمحترف والسينيور:
🔴 التلميذ: بيكتب المطلوب وخلاص
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
// ← هيطبعلك: "GET /api/users"
// ← لا وقت، ولا Status Code، ولا تعرف الريكويست خلص ولا لسه
// ← لو حصل مشكلة في الـ Production، مش هتعرف تتبعها
🟡 المطور الشاطر: فاهم إنه محتاج يديك أكتر
function logger(req, res, next) {
const start = Date.now();
console.log(`→ ${req.method} ${req.url}`);
const originalEnd = res.end;
res.end = function (...args) {
const ms = Date.now() - start;
console.log(`← ${res.statusCode} [${ms}ms]`);
originalEnd.apply(res, args);
};
next();
}
// ← هيطبعلك: "→ GET /api/users"
// ← وبعد ما الريكويست تخلص: "← 200 [45ms]"
// ← كده عرفت الريكويست أخدت قد إيه — بس لو عندك 1000 ريكويست في الثانية، مش هتعرف تربطهم ببعض
🟢 السينيور: بيفكر في الـ Debugging وإن السيستم هيتطور لـ Microservices
const crypto = require('crypto');
function logger(req, res, next) {
// ① بيدي ID فريد لكل Request — وده الحاجة اللي بتفرق في الـ Production
const requestId = crypto.randomUUID();
const start = Date.now();
// ② بيلزق الـ ID في الـ req عشان أي Error Handler أو Service يقدر يستخدمه
req.requestId = requestId;
// ③ بيكتب Structured JSON Logs — مش نص عادي
console.log(JSON.stringify({
type: 'REQUEST_IN',
id: requestId,
method: req.method,
url: req.url,
ip: req.socket?.remoteAddress,
userAgent: req.headers['user-agent'],
timestamp: new Date().toISOString(),
}));
const originalEnd = res.end;
res.end = function (...args) {
const ms = Date.now() - start;
console.log(JSON.stringify({
type: 'REQUEST_OUT',
id: requestId, // ← نفس الـ ID عشان نربط الدخول بالخروج
status: res.statusCode,
duration: ms,
// ④ لو الريكويست أخدت أكتر من 3 ثواني — ده Warning
slow: ms > 3000,
}));
originalEnd.apply(res, args);
};
// ⑤ بيمرر الـ Request ID في الـ Response Header عشان الـ Frontend يبعته لو حصل مشكلة
res.setHeader('X-Request-Id', requestId);
next();
}
// ← كده الـ Logs بتاعتك JSON جاهز يترمي على Datadog أو ELK أو Grafana
// ← تقدر تعمل Search بالـ Request ID وتشوف مسار الريكويست من بدايته لنهايته
// ← لو الريسبونس بطيء، الـ "slow: true" هيظهر في Dashboard وينبهك
// ← الـ Frontend لو اليوزر بلّغ عن مشكلة، بيبعتلك الـ X-Request-Id وتتبعه فوراً
💡 الفرق الحقيقي مش في الكود — في العقلية. التلميذ بيكتب Logger يشوف بيه وهو بيطور. الشاطر بيكتب Logger يفيده في الـ Debugging. السينيور بيكتب Logger يفيد الفريق كله في الـ Production لما الساعة 3 الفجر السيستم يقع وكل الناس بتدور على السبب.
8. أربع Middlewares جاهزين للـ Production — خدهم واشتغل بيهم
① Rate Limiter — بيحمي سيرفرك من البلطجة والـ DDoS
const limits = new Map(); // IP → { count, resetAt }
function rateLimiter({ limit = 100, windowMs = 60_000 } = {}) {
// Factory Pattern — بترجعلك الـ Middleware Function
return function (req, res, next) {
const ip = req.socket?.remoteAddress ?? 'unknown';
const now = Date.now();
const record = limits.get(ip);
if (!record || now > record.resetAt) {
// أول ريكويست من الـ IP ده أو العداد اتصفّر بعد الوقت
limits.set(ip, { count: 1, resetAt: now + windowMs });
return next();
}
if (record.count >= limit) {
// المعلم عدا الليميت — نقفل في وشه ونقوله يستنى
const retryAfter = Math.ceil((record.resetAt - now) / 1000);
res.writeHead(429, {
'Content-Type': 'application/json',
'Retry-After': retryAfter,
'X-RateLimit-Limit': limit,
'X-RateLimit-Remaining': 0,
});
return res.end(JSON.stringify({
error: 'براحة على السيرفر شوية الله يخليك',
retryAfter: `${retryAfter} ثانية`,
}));
}
record.count++;
// ← بنقول للـ Client فاضلك كام ريكويست — ده Professional API Design
res.setHeader('X-RateLimit-Remaining', limit - record.count);
next();
};
}
// استخدامها:
const defaultLimit = rateLimiter(); // 100 ريكويست في الدقيقة
const strictLimit = rateLimiter({ limit: 5, windowMs: 15 * 60_000 }); // 5 محاولات لكل ربع ساعة (Login)
⚠️ ملاحظة Production: الـ Map في الميموري كويس للتجربة، بس في الـ Production الحقيقي لو عندك أكتر من سيرفر (Horizontal Scaling)، لازم تستخدم Redis عشان كل السيرفرات تشوف نفس العداد. غير كده كل سيرفر هيعد لوحده والهاكر يوزع ريكويستاته على السيرفرات ويعدي.
② Auth Guard — حراسة الـ JWT
function requireAuth(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (!token) {
res.writeHead(401, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({
error: 'معلش سجل دخول الأول',
code: 'MISSING_TOKEN',
}));
}
try {
const decoded = verifyToken(token);
req.user = decoded; // ← لزقنا بيانات اليوزر في الـ req — أي Middleware بعد كده تقدر تستخدمها
next();
} catch (err) {
const isExpired = err.name === 'TokenExpiredError';
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: isExpired ? 'السيشن خلصت، افتحها تاني' : 'التوكن ده مضروب',
code: isExpired ? 'TOKEN_EXPIRED' : 'INVALID_TOKEN',
}));
}
}
والـ Admin Guard — مثال على الـ Middleware Composition:
function requireAdmin(req, res, next) {
// ← بنندي requireAuth الأول — لو فشلت مش هتوصل لهنا
requireAuth(req, res, () => {
if (req.user.role !== 'admin') {
res.writeHead(403, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'ملعبكش، ده مكان للكبار فقط' }));
}
next(); // ← إنت Admin؟ اتفضل
});
}
🧩 لاحظ الـ Pattern هنا: الـ
requireAdminمش بتعيد كتابة كود الـ Auth — هي بتندهrequireAuthوبتديهاnextمخصوص. ده اللي بنسميه Middleware Composition — بناء Middleware معقد من Middlewares أبسط. ده نفس مبدأ الـ Function Composition في الـ Functional Programming.
③ Body Parser — بيفك طلاسم الـ JSON
function parseBody(options = {}) {
const maxSize = options.maxBytes ?? 1_048_576; // 1 ميجا افتراضي
return async function (req, res, next) {
// بيشتغل بس مع الـ Methods اللي بتبعت داتا
if (!['POST', 'PUT', 'PATCH'].includes(req.method)) {
return next();
}
const contentType = req.headers['content-type'] ?? '';
if (!contentType.includes('application/json')) {
return next(); // مش JSON — نسيبها لـ handler تاني
}
try {
const chunks = [];
let totalSize = 0;
for await (const chunk of req) {
totalSize += chunk.length;
if (totalSize > maxSize) {
req.destroy(); // ← بنقتل الاتصال عشان ميكملش يبعتلنا بايتات
res.writeHead(413, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'الريكويست حجمه كبير أوي' }));
}
chunks.push(chunk);
}
const raw = Buffer.concat(chunks).toString('utf-8');
req.body = raw ? JSON.parse(raw) : null;
next();
} catch {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'الـ JSON ده مكتوب غلط يعم' }));
}
};
}
④ CORS Handler — عشان الـ Frontend يقدر يكلمك من Domain تاني
function cors(options = {}) {
const allowedOrigins = options.origins ?? ['*'];
const allowedMethods = options.methods ?? ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
return function (req, res, next) {
const origin = req.headers['origin'];
// بنشيك هل الـ Origin ده مسموح ولا لأ
const isAllowed = allowedOrigins.includes('*') || allowedOrigins.includes(origin);
if (isAllowed && origin) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', allowedMethods.join(', '));
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// الـ Preflight Request — المتصفح بيبعته قبل الريكويست الحقيقي
if (req.method === 'OPTIONS') {
res.writeHead(204); // No Content — فاضي بس الـ Headers فوق كافية
return res.end();
}
next();
};
}
// استخدامها:
const devCors = cors(); // كل حاجة مسموحة — للـ Development
const prodCors = cors({ origins: ['https://myapp.com', 'https://admin.myapp.com'] });
💡 ليه CORS مهم؟ المتصفح بطبيعته بيمنع أي JavaScript على Domain معين إنها تكلم Domain تاني (ده اسمه Same-Origin Policy). الـ CORS Middleware بيقول للمتصفح “أنا موافق إن الـ Domain ده يكلمني.” من غيره، الـ Frontend بتاعك مش هيقدر يكلم الـ API أصلاً — حتى لو الـ API شغال.
9. طريقك يا مفتوح يا مقفول — Success & Failure Paths
الـ Pipeline ليها مسارات مختلفة، ومهم تفهم إزاي السيرفر بيتصرف وقت الخطأ:
┌──────────────────┐
REQUEST ──────────► │ logger() │────────────────────────► next()
└──────────────────┘ │
▼
┌──────────────────┐ ┌──────────────────┐
│ rateLimiter() │ ──استهبلت───────► │ res(429) STOP │
└────────┬─────────┘ └──────────────────┘
│ next()
▼
┌──────────────────┐ ┌──────────────────┐
│ requireAuth() │ ──مفيش توكن──────► │ res(401) STOP │
└────────┬─────────┘ └──────────────────┘
│ next()
▼
┌──────────────────┐ ┌──────────────────┐
│ parseBody() │ ──بدعت في JSON──► │ res(400) STOP │
└────────┬─────────┘ └──────────────────┘
│ next()
▼
┌──────────────────┐
│ controller() │ ────────────────────────► res(200) ✅
└────────┬─────────┘
│ throw Error أو next(err)
▼
┌──────────────────┐
│ globalError │ ────────────────────────► res(5xx) ❌
│ Handler() │
└──────────────────┘
3 سيناريوهات للرد:
| السيناريو | إيه بيحصل | مثال |
|---|---|---|
| قفل من محطة في النص | Middleware لاقت مشكلة وردت قبل الـ Controller | 401 (مفيش توكن)، 429 (Rate Limit)، 400 (JSON باظ) |
| رد ناجح من الـ Controller | كل الـ Middlewares عدت والـ Controller اشتغل | 200، 201 |
| رد طوارئ next(err) | حصل Exception في الـ Controller أو Service | 500 (Bug)، 503 (Service Down) |
10. السر اللي مش كل الناس عارفه: الـ Error Handler بتاخد 4 باراميتر ليه؟
لو أخدت بالك:
// ✅ Normal Middleware — بتاخد 3 باراميتر
function logger(req, res, next) { ... }
// ✅ Error Middleware — الوحيدة اللي بتاخد 4 (err أول واحد)
function globalErrorHandler(err, req, res, next) { ... }
ده مش مزاج ولا تقليد. الـ Pipeline بتعرف الـ Error Middleware عن طريق العدد بس — لو fn.length === 4 يبقى ده Error Handler.
ولما بتنده next(err):
function next(maybeError) {
if (maybeError) {
// ① بدور على أول Function بتاخد 4 باراميتر
const errorHandler = middlewares.find(fn => fn.length === 4);
// ② برميك عليها مباشرة — بنفض لكل الـ Middlewares في النص
return errorHandler(maybeError, req, res, next);
}
// الطريق العادي...
}
🤯 يعني Express بتستخدم
fn.lengthتعرف الفرق! وده حاجة غريبة شوية — لأن لو كتبت الـ Error Handler بـ Arrow Function وعملت Destructuring أو Default Parameters، الـlengthممكن يتغير والـ Express مش هتعرفها كـ Error Handler! عشان كده دايماً اكتب الـ Error Handler كـ Regular Function Expression بـ 4 Parameters صريحين.
مثال عملي:
function validateInput(req, res, next) {
if (!req.body?.email) {
return next(new Error('يا باشا الايميل مطلوب'));
// ← next(err) → نط فوراً للـ Error Handler
}
next(); // ← next() عادية — كمل طريقك
}
function globalErrorHandler(err, req, res, next) {
// ← بنسجل الـ Error مع الـ Request ID عشان نقدر نتبعه
console.error(JSON.stringify({
type: 'ERROR',
requestId: req.requestId,
message: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
timestamp: new Date().toISOString(),
}));
const statusCode = err.statusCode || 500;
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: statusCode >= 500 ? 'حصل مشكلة في السيرفر' : err.message,
// ← في الـ Production مبنبعتش الـ Stack Trace للعميل — ده يسرب معلومات
requestId: req.requestId, // ← بس بنبعت الـ ID عشان يقولنا عليه لو بلّغ
}));
}
11. الـ Middleware على أرض الواقع — الـ Frameworks بتلعبها إزاي
Express — الأب الروحي
const express = require('express');
const app = express();
// Global Middlewares — شغالة على كل ريكويست بدون استثناء
app.use(logger); // ① أول حاجة: سجل كل حاجة
app.use(cors({ origins: ['https://myapp.com'] })); // ② CORS
app.use(rateLimiter()); // ③ Rate Limiting
app.use(express.json()); // ④ Body Parser (ده الـ Built-in بتاع Express)
// Route-specific Middlewares — لـ Routes معينة بس
app.get('/api/users', requireAuth, usersController.getAll);
app.post('/api/orders', requireAuth, parseBody(), ordersController.create);
app.delete('/api/users', requireAuth, requireAdmin, usersController.delete);
// ↑ ↑
// متطلب أساسي شروط إضافية للـ Route ده بس
// Error Handler — لازم يبقى في الآخر خالص
app.use(globalErrorHandler); // ← الـ 4 باراميتر بتوعنا
لو شغال من غير Framework — نفس الدماغ
const http = require('http');
const globalMiddlewares = [logger, cors(), rateLimiter()];
const protectedMiddlewares = [...globalMiddlewares, requireAuth];
const routes = {
'GET /api/users': { mw: protectedMiddlewares, handler: usersController.getAll },
'POST /api/auth': { mw: [logger, cors(), rateLimiter({ limit: 5 })], handler: authController.login },
'DELETE /api/users': { mw: [...protectedMiddlewares, requireAdmin], handler: usersController.delete },
};
const server = http.createServer((req, res) => {
const key = `${req.method} ${req.url}`;
const route = routes[key];
if (!route) {
res.writeHead(404, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'الطريق ده مش موجود يا هندسة' }));
}
runPipeline(req, res, route.mw, route.handler);
});
12. الـ Middleware في الحياة الحقيقية — أنماط متقدمة من الـ Production
النمط ①: الـ Conditional Middleware — شغلها على Routes معينة بس
function unlessPath(paths, middleware) {
return function (req, res, next) {
// لو الـ Path في القائمة البيضاء — عدّيه من غير ما تنده الـ Middleware
if (paths.includes(req.url)) {
return next();
}
middleware(req, res, next);
};
}
// كده الـ Auth هتشتغل على كل حاجة ماعدا الـ Login والـ Register
app.use(unlessPath(['/api/auth/login', '/api/auth/register'], requireAuth));
النمط ②: الـ Timing Middleware — بتقيس أداء كل Route لوحده
function timing(req, res, next) {
const start = process.hrtime.bigint(); // ← دقة بالنانو ثانية
res.on('finish', () => {
const duration = Number(process.hrtime.bigint() - start) / 1_000_000;
// بنبعت الـ Metrics للـ Monitoring
metrics.recordRequestDuration(req.method, req.url, res.statusCode, duration);
// لو الريكويست أخدت أكتر من 2 ثانية — ده Warning
if (duration > 2000) {
console.warn(JSON.stringify({
type: 'SLOW_REQUEST',
method: req.method,
url: req.url,
duration: `${duration.toFixed(2)}ms`,
requestId: req.requestId,
}));
}
});
next();
}
النمط ③: الـ Request Validation Middleware — باستخدام Schema
function validateBody(schema) {
return function (req, res, next) {
const { error, value } = schema.validate(req.body);
if (error) {
res.writeHead(422, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({
error: 'الداتا اللي بعتها فيها مشكلة',
details: error.details.map(d => d.message),
}));
}
req.body = value; // ← الـ Validated والـ Sanitized version
next();
};
}
// استخدامها:
const Joi = require('joi');
const createUserSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).optional(),
});
app.post('/api/users', validateBody(createUserSchema), usersController.create);
13. ساحة الاختبار — أسئلة إنترفيو حقيقية
أسئلة هتيجيلك وش:
١ — إيه اللي يحصل لو نسيت تنده next() ونسيت ترد بـ res جوا الـ Middleware؟
الريكويست هتفضل متعلقة (Hanging) لحد ما العميل يعمل Timeout. والأخطر: الـ Connection مش بتتقفل فبتتراكم في الميموري. لو ده حصل مع Traffic عالي، السيرفر هيعمل Memory Leak ويقع. القاعدة: دايماً لازم تختار يا
next()ياres.end().
٢ — ينفع Middleware تشتغل بعد ما الـ Response يتبعت؟
ينفع تكنيكالي — عن طريق Hook على
res.on('finish', callback). الـ Finish event بيحصل بعد ما الـ Response يتبعت بالكامل. ده مفيد عشان تسجل Metrics أو تبعت Analytics من غير ما تأخر الـ Response نفسه. بس متحاولش تكتب على الـresبعد ما اتبعت — ده هيعمل Error.
٣ — إيه الفرق بين next() و next(err) و next('route')؟
النداء المعنى إيه بيحصل next()”أنا خلصت، كمل عادي” بينده الـ Middleware اللي بعدي في نفس الـ Route next(err)”حصلت مصيبة!” بينط فوراً لأول Error Handler (الـ 4 Parameters) next('route')”سيب الـ Route ده” (Express بس) بيسيب باقي Middlewares الـ Route الحالي وينط للـ Route اللي بعده
٤ — إزاي تتعامل مع Async Middleware في Express؟
Express مش بيمسك Promise Rejections تلقائياً (على الأقل في Express 4). لو الـ Middleware بتاعتك
asyncوعملتthrow، الـ Error مش هيوصل للـ Error Handler — هيتجاهل والريكويست هتتعلق! الحل:// Wrapper عشان يمسك الـ Async Errors function asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; } // استخدامه: app.get('/api/users', asyncHandler(async (req, res) => { const users = await db.getUsers(); // لو دي عملت throw، الـ Error هيتمسك res.json(users); }));ملاحظة: Express 5 (اللي هيطلع قريب) بيمسك ده تلقائي.
٥ — إيه ترتيب الـ Middlewares الصح في الـ Production؟
1. CORS ← لازم يبقى أول حاجة عشان الـ Preflight يترد فوراً 2. Logger ← عشان يسجل كل ريكويست حتى اللي هيتقفل بعد كده 3. Rate Limiter ← عشان يوقف الـ Spam قبل ما يدخل أي Logic 4. Body Parser ← عشان يقرأ الـ Body قبل ما الـ Auth تحتاجه 5. Auth Guard ← عشان يتأكد مين اللي بيكلمنا 6. Validation ← عشان يتأكد الداتا صح 7. Controller ← الشغل الحقيقي 8. Error Handler ← لازم يبقى آخر حاجة — بيمسك أي حاجة وقعتالترتيب مهم جداً! لو حطيت الـ Body Parser بعد الـ Auth، مش هتقدر تقرأ الـ Body في الـ Login Endpoint. لو حطيت الـ Logger بعد الـ Rate Limiter، مش هتسجل الـ Requests اللي اترفضت.
