← Назад к вопросам

Как реализовать rate limiting для защиты Node.js API от злоупотреблений?

2.0 Middle🔥 231 комментариев
#Безопасность#Кэширование и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать rate limiting для защиты Node.js API от злоупотреблений?

Rate limiting — это ограничение количества запросов, которые клиент может сделать за определённый период. Это защита от DDoS атак, brute force и избыточного использования ресурсов.

Простой пример с express-rate-limit

import express from 'express';
import rateLimit from 'express-rate-limit';

const app = express();

// Базовый rate limiter: 100 запросов за 15 минут
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // Лимит запросов
  message: 'Too many requests, please try again later',
  standardHeaders: true,     // Вернуть информацию в RateLimit-* headers
  legacyHeaders: false       // Отключить X-RateLimit-* headers
});

// Применить ко всем запросам
app.use(limiter);

app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

Разные лимиты для разных endpoint'ов

const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});

const strictLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5 // Строгий лимит для login
});

const apiLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 30 // Более мягкий для публичного API
});

app.use(globalLimiter); // Все запросы
app.post('/login', strictLimiter, (req, res) => {}); // Login
app.get('/api/data', apiLimiter, (req, res) => {}); // API

Rate limiting по IP и User ID

const userLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  keyGenerator: (req) => {
    // Лимитировать по User ID если аутентифицирован
    return req.user?.id || req.ip;
  },
  skip: (req) => {
    // Не лимитировать админов
    return req.user?.role === 'admin';
  }
});

app.use(userLimiter);

Redis store для распределённых систем

import RedisStore from 'rate-limit-redis';
import redis from 'redis';

const redisClient = redis.createClient();

const limiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:' // Rate limit prefix
  }),
  windowMs: 15 * 60 * 1000,
  max: 100
});

app.use(limiter);

// Теперь лимиты работают МЕЖДУ серверами!

Кастомный rate limiter с Redis

import redis from 'redis';

const redisClient = redis.createClient();

async function checkRateLimit(
  key: string,
  limit: number,
  windowMs: number
): Promise<{ allowed: boolean; remaining: number; resetTime: number }> {
  const current = await redisClient.incr(key);
  
  if (current === 1) {
    // Первый запрос - установить TTL
    await redisClient.expire(key, Math.ceil(windowMs / 1000));
  }
  
  const ttl = await redisClient.ttl(key);
  const allowed = current <= limit;
  const remaining = Math.max(0, limit - current);
  const resetTime = Date.now() + (ttl * 1000);
  
  return {
    allowed,
    remaining,
    resetTime
  };
}

app.use(async (req, res, next) => {
  const key = `rate-limit:${req.ip}`;
  const result = await checkRateLimit(key, 100, 15 * 60 * 1000);
  
  res.set('RateLimit-Limit', '100');
  res.set('RateLimit-Remaining', result.remaining.toString());
  res.set('RateLimit-Reset', (result.resetTime / 1000).toString());
  
  if (!result.allowed) {
    return res.status(429).json({
      error: 'Too many requests',
      retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000)
    });
  }
  
  next();
});

Слайдирующее окно (Sliding Window)

// Более точный метод - удаляет старые запросы
async function slidingWindowRateLimit(
  key: string,
  limit: number,
  windowMs: number
): Promise<boolean> {
  const now = Date.now();
  const windowStart = now - windowMs;
  
  // Удалить старые записи
  await redisClient.zremrangebyscore(key, 0, windowStart);
  
  // Подсчитать запросы в окне
  const count = await redisClient.zcard(key);
  
  if (count < limit) {
    // Добавить текущий запрос
    await redisClient.zadd(key, now, `${now}-${Math.random()}`);
    await redisClient.expire(key, Math.ceil(windowMs / 1000));
    return true;
  }
  
  return false;
}

Обработка ошибок rate limiting

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  handler: (req, res) => {
    res.status(429).json({
      error: {
        message: 'Too many requests',
        status: 429,
        retryAfter: req.rateLimit.resetTime
          ? Math.ceil((req.rateLimit.resetTime - Date.now()) / 1000)
          : 900 // 15 minutes
      }
    });
  },
  onLimitReached: (req, res, options) => {
    // Логируем когда лимит превышен
    console.warn(`Rate limit reached for IP: ${req.ip}`);
    // Можем отправить алерт
  }
});

Rate limiting по типам операций

const readLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 1000 // Много читаем
});

const writeLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 50 // Мало пишем (дороже)
});

const deleteLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 10 // Очень осторожно с удалениями
});

app.get('/api/data', readLimiter, (req, res) => {});
app.post('/api/data', writeLimiter, (req, res) => {});
app.delete('/api/data/:id', deleteLimiter, (req, res) => {});

Production-ready стратегия

import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import redis from 'redis';

const redisClient = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

// Разные лимиты
const limits = {
  public: rateLimit({
    store: new RedisStore({ client: redisClient, prefix: 'rl:public:' }),
    windowMs: 1 * 60 * 1000,
    max: 100
  }),
  
  authenticated: rateLimit({
    store: new RedisStore({ client: redisClient, prefix: 'rl:auth:' }),
    windowMs: 1 * 60 * 1000,
    max: 500,
    keyGenerator: (req) => req.user?.id || req.ip
  }),
  
  login: rateLimit({
    store: new RedisStore({ client: redisClient, prefix: 'rl:login:' }),
    windowMs: 15 * 60 * 1000,
    max: 5,
    skipSuccessfulRequests: true // Не считать успешные логины
  }),
  
  payment: rateLimit({
    store: new RedisStore({ client: redisClient, prefix: 'rl:payment:' }),
    windowMs: 24 * 60 * 60 * 1000,
    max: 10 // 10 платежей в день
  })
};

app.post('/login', limits.login, (req, res) => {});
app.use('/api/public', limits.public);
app.use('/api/user', limits.authenticated);
app.post('/api/payment', limits.payment, (req, res) => {});

Мониторинг rate limiting

setInterval(async () => {
  const stats = await redisClient.keys('rl:*');
  console.log(`Active rate limit keys: ${stats.length}`);
}, 60000);

Вывод

Rate limiting важен для:

  1. Защиты от DDoS — ограничить количество запросов
  2. Защиты от Brute Force — ограничить попытки логина
  3. Справедливого распределения — все клиенты имеют равный доступ
  4. Контроля затрат — API calls стоят денег
  5. Стабильности — предотвратить перегрузку сервера