← All posts
rate limitingSaaS APIindie developerAPI securityNode.js

Rate Limiting SaaS API: A Step-by-Step Guide for Indie Makers

O

OverMCP Team

Rate limiting is essential for any SaaS API to protect against abuse, ensure fair usage, and maintain performance. This guide walks you through implementing rate limiting for your indie SaaS API, with practical code examples and strategies.

TL;DR: How to Add Rate Limiting to Your SaaS API

Rate limiting controls how many requests a client can make to your API within a given time window. For an indie SaaS, a simple token bucket or sliding window algorithm using Redis or in-memory storage is sufficient. Start with per-user limits (e.g., 1000 requests/hour) and apply per-endpoint limits for sensitive routes.

Why Rate Limiting Matters for Indie SaaS APIs

Without rate limiting, a single user or bot can overwhelm your API, causing slowdowns or outages for everyone. This is especially critical for indie makers who run on limited infrastructure. Rate limiting:

  • Prevents abuse (e.g., scrapers, brute-force attacks)
  • Ensures fair resource distribution among users
  • Protects your backend from overload
  • Helps you scale predictably
  • Choosing a Rate Limiting Strategy

    Token Bucket

    Allows bursts up to a certain size. A token is added every N seconds; a request consumes a token. If no tokens are left, the request is denied.

    Sliding Window Log

    Tracks timestamps of requests in a window (e.g., last hour). More memory-intensive but accurate.

    Sliding Window Counter

    Combines fixed window with a sliding approach using counters in Redis sorted sets or hashes. Good balance of accuracy and performance.

    For most indie apps, a simple token bucket or sliding window counter is ideal.

    Step-by-Step Implementation with Node.js and Express

    1. Basic In-Memory Token Bucket (for prototyping)

    const rateLimit = {};
    
    function tokenBucket(key, maxTokens, refillTimeMs, refillAmount) {
      const now = Date.now();
      if (!rateLimit[key]) {
        rateLimit[key] = { tokens: maxTokens, lastRefill: now };
      }
      const bucket = rateLimit[key];
      const elapsed = now - bucket.lastRefill;
      const refillTokens = Math.floor(elapsed / refillTimeMs) * refillAmount;
      if (refillTokens > 0) {
        bucket.tokens = Math.min(bucket.tokens + refillTokens, maxTokens);
        bucket.lastRefill = now;
      }
      if (bucket.tokens > 0) {
        bucket.tokens--;
        return true;
      }
      return false;
    }

    2. Using Redis for Production (with `ioredis`)

    Redis is recommended for production as it persists state and works across multiple instances.

    const Redis = require('ioredis');
    const redis = new Redis();
    
    const WINDOW_SIZE = 60; // seconds
    const MAX_REQUESTS = 100;
    
    async function slidingWindowRateLimit(userId) {
      const key = `rate:${userId}`;
      const now = Date.now();
      const windowStart = now - WINDOW_SIZE * 1000;
    
      // Remove old entries
      await redis.zremrangebyscore(key, 0, windowStart);
    
      // Count current window requests
      const count = await redis.zcard(key);
    
      if (count >= MAX_REQUESTS) {
        return false;
      }
    
      // Add current request
      await redis.zadd(key, now, `${now}-${Math.random()}`);
      await redis.expire(key, WINDOW_SIZE);
    
      return true;
    }

    3. Express Middleware Example

    const rateLimitMiddleware = async (req, res, next) => {
      const userId = req.user?.id || req.ip;
      const allowed = await slidingWindowRateLimit(userId);
      if (!allowed) {
        return res.status(429).json({ error: 'Too many requests' });
      }
      next();
    };
    
    app.use('/api', rateLimitMiddleware);

    Best Practices for Rate Limiting SaaS API

  • Set per-user limits: Use API keys or user IDs to track.
  • Use per-endpoint limits: More restrictive on auth, less on public data.
  • Return proper headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
  • Provide informative error responses: Include retry-after time.
  • Start generous and tighten: Monitor usage and adjust.
  • Consider burst allowance: Let users exceed limit briefly once.
  • Real-World Example: Adding Rate Limiting to a Strapi API

    Strapi is popular among indie makers. To add rate limiting:

  • Install koa-ratelimit (since Strapi uses Koa).
  • Configure in middlewares.js:
  • module.exports = {
      settings: {
        rateLimit: {
          enabled: true,
          interval: { min: 15 },
          max: 100,
          headers: true,
          driver: 'memory',
        },
      },
    };

    How OverMCP Helps

    OverMCP (overmcp.com) scans your vibe-coded apps for security issues, including missing rate limiting. It can detect endpoints that lack protection and suggest configurations tailored to your stack.

    FAQ

    What is the best rate limiting algorithm for a small SaaS?

    For small to medium SaaS, the sliding window counter with Redis is a great balance of accuracy and resource usage. It prevents spikes and is easy to implement.

    How do I set rate limits for different pricing tiers?

    Store tier limits in your user database or a config file. In your middleware, fetch the user's tier and apply the corresponding limit (e.g., free: 100 req/h, pro: 1000 req/h).

    Should I rate limit by IP or by user?

    By user (API key or user ID) is preferred for authenticated endpoints. For public endpoints, fallback to IP. This prevents one user's session from affecting others behind the same IP.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free