Rate Limiting SaaS API: A Step-by-Step Guide for Indie Makers
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:
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
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.Real-World Example: Adding Rate Limiting to a Strapi API
Strapi is popular among indie makers. To add rate limiting:
koa-ratelimit (since Strapi uses Koa).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.