الخطوات
-
1
تثبيت express-rate-limit
express-rate-limitهي الحزمة القياسية لهذا الغرض. يحظى بدعم من منظومة Express ويغطي الحالات الشائعة بأدنى قدر من الإعداد.bashnpm install express-rate-limit -
2
تطبيق حد عام على جميع المسارات
يمنع الحد العام الفضفاض العملاء المتهورين من إغراق الخادم قبل وصولهم إلى نقاط حساسة. أضفه قبل تعريفات المسارات.
javascriptconst express = require('express'); const rateLimit = require('express-rate-limit'); const app = express(); const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 دقيقة max: 200, // عدد الطلبات لكل نافذة زمنية لكل IP standardHeaders: true, // إرسال ترويسات RateLimit-* legacyHeaders: false, message: { error: { code: 'TOO_MANY_REQUESTS', message: 'تمهّل.' } }, }); app.use(globalLimiter); -
3
تشديد الحد على نقاط المصادقة
نقاط تسجيل الدخول وإعادة تعيين كلمة المرور هي أهداف للقوة الغاشمة. طبّق معدلاً أكثر صرامة مباشرةً على هذه المسارات. خمس محاولات في 15 دقيقة حد معقول لنقطة تسجيل الدخول.
javascriptconst authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, skipSuccessfulRequests: true, message: { error: { code: 'AUTH_RATE_LIMITED', message: 'محاولات تسجيل دخول كثيرة جداً. أعد المحاولة بعد 15 دقيقة.', }, }, }); app.post('/auth/login', authLimiter, loginHandler); app.post('/auth/forgot-password', authLimiter, forgotPasswordHandler); -
4
التمييز بين IP ومعرّف المستخدم
المفتاح الافتراضي هو عنوان IP. في المسارات المصادَق عليها، يمنع استخدام معرّف المستخدم كمفتاح تجاوزَ الحد عبر تغيير عناوين IP. أعد معرّف المستخدم إن وُجد، وإلا فارجع إلى IP.
javascriptconst userAwareLimiter = rateLimit({ windowMs: 60 * 1000, max: 60, keyGenerator: (req) => { return req.user ? `user:${req.user.id}` : req.ip; }, }); app.use('/api', authenticateOptional, userAwareLimiter); -
5
فهم ترويسات استجابة RateLimit
عند تفعيل
standardHeaders: true، تحمل كل استجابة:RateLimit-Limit— الحد الأقصى للطلبات المسموح بهاRateLimit-Remaining— الطلبات المتبقية في النافذة الحاليةRateLimit-Reset— توقيت Unix لإعادة تعيين النافذة
عند تجاوز الحد، ترسل المكتبة HTTP 429 وترويسة
Retry-After. العملاء المحترمون يحترمونها؛ سجّل من لا يلتزم وافصله. -
6
استخدام Redis لبيئات متعددة الحالات
التخزين الافتراضي في الذاكرة لا يشارك الحالة بين العمليات أو الأجهزة. إذا شغّلت أكثر من عملية Node واحدة (cluster أو PM2 أو Kubernetes)، يحسب كل مثيل بشكل مستقل، مما يُضاعف الحد فعلياً. انتقل إلى مخزن مدعوم بـ Redis.
bashnpm install rate-limit-redis ioredis -
7
إعداد مخزن Redis
مرّر خيار المخزن إلى المُقيِّد. سيعيش العداد الآن في Redis ويُشارَك بين جميع الحالات.
javascriptconst { RedisStore } = require('rate-limit-redis'); const Redis = require('ioredis'); const redisClient = new Redis(process.env.REDIS_URL); const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 200, standardHeaders: true, legacyHeaders: false, store: new RedisStore({ sendCommand: (...args) => redisClient.call(...args), }), }); -
8
تسجيل أحداث تجاوز الحد ومراقبتها
معرفة من يتجاوز الحود لا تقل أهمية عن تطبيقها. تجاوز
handlerبدالة تُسجّل الحدث قبل إرسال استجابة 429. أدخل هذه السجلات إلى نظام المراقبة (Datadog أو Grafana أو Logtail) وأنشئ تنبيهات على الارتفاعات المفاجئة.javascriptconst logger = require('./logger'); const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 200, standardHeaders: true, legacyHeaders: false, handler: (req, res, next, options) => { logger.warn({ event: 'rate_limit_exceeded', ip: req.ip, userId: req.user?.id ?? null, path: req.path, method: req.method, }); res.status(options.statusCode).json(options.message); }, });
نصائح ومحاذير
- اضبط <code>trustProxy: true</code> أو <code>app.set('trust proxy', 1)</code> عندما يعمل تطبيقك خلف Nginx أو موازن تحميل، وإلا سيكون <code>req.ip</code> دائماً عنوان الوكيل.
- استخدم <code>skip</code> لاستثناء مسارات فحص الصحة الداخلية أو عناوين IP لمنظومة المراقبة كي لا تُفعّل الحد أبداً.
- التخزين في الذاكرة مناسب لبيئة التطوير أحادية العملية، لكن استخدم Redis دائماً في بيئة الاختبار والإنتاج.
- لا تضع حدوداً مشددة إلى درجة تُفعَّل في الاستخدام العادي — حلّل أنماط حركة المرور الفعلية أولاً.
خاتمة
تحديد معدل الطلبات ضرورة أساسية لأي واجهة API عامة. طبّق حداً عاماً متساهلاً، وحداً صارماً على مسارات المصادقة، وميّز بين المستخدمين باستخدام معرّفاتهم عند المصادقة، ثم ادعمه بـ Redis فور التوسع إلى أكثر من عملية واحدة. يستغرق الإعداد بالكامل أقل من ساعة ويحميك من طيف واسع من الإساءات والأحمال غير المتوقعة.