logo

Rate Limiting

Implement simple rate limiting using cache TTL.

Basic Pattern

import { MemoryCache } from '@humanspeak/memory-cache'

interface RateLimitEntry {
    count: number
    resetAt: number
}

const rateLimitCache = new MemoryCache<RateLimitEntry>({
    maxSize: 100000,
    ttl: 60 * 1000  // 1 minute window
})

function checkRateLimit(clientId: string, limit: number = 100): boolean {
    const key = `ratelimit:${clientId}`
    const entry = rateLimitCache.get(key)

    if (!entry) {
        // First request
        rateLimitCache.set(key, {
            count: 1,
            resetAt: Date.now() + 60000
        })
        return true
    }

    if (entry.count >= limit) {
        return false  // Rate limit exceeded
    }

    // Increment counter
    rateLimitCache.set(key, {
        count: entry.count + 1,
        resetAt: entry.resetAt
    })

    return true
}
import { MemoryCache } from '@humanspeak/memory-cache'

interface RateLimitEntry {
    count: number
    resetAt: number
}

const rateLimitCache = new MemoryCache<RateLimitEntry>({
    maxSize: 100000,
    ttl: 60 * 1000  // 1 minute window
})

function checkRateLimit(clientId: string, limit: number = 100): boolean {
    const key = `ratelimit:${clientId}`
    const entry = rateLimitCache.get(key)

    if (!entry) {
        // First request
        rateLimitCache.set(key, {
            count: 1,
            resetAt: Date.now() + 60000
        })
        return true
    }

    if (entry.count >= limit) {
        return false  // Rate limit exceeded
    }

    // Increment counter
    rateLimitCache.set(key, {
        count: entry.count + 1,
        resetAt: entry.resetAt
    })

    return true
}

Express Middleware

import { MemoryCache } from '@humanspeak/memory-cache'
import type { Request, Response, NextFunction } from 'express'

// Uses RateLimitEntry type and checkRateLimit function from Basic Pattern above

const rateLimitCache = new MemoryCache<RateLimitEntry>({
    maxSize: 100000,
    ttl: 60 * 1000
})

function rateLimitMiddleware(limit: number = 100) {
    return (req: Request, res: Response, next: NextFunction) => {
        const clientId = req.headers['x-client-id'] as string || req.ip

        if (!checkRateLimit(clientId, limit)) {
            return res.status(429).json({
                error: 'Too Many Requests',
                retryAfter: 60
            })
        }

        next()
    }
}

// Usage
app.use('/api', rateLimitMiddleware(100))
import { MemoryCache } from '@humanspeak/memory-cache'
import type { Request, Response, NextFunction } from 'express'

// Uses RateLimitEntry type and checkRateLimit function from Basic Pattern above

const rateLimitCache = new MemoryCache<RateLimitEntry>({
    maxSize: 100000,
    ttl: 60 * 1000
})

function rateLimitMiddleware(limit: number = 100) {
    return (req: Request, res: Response, next: NextFunction) => {
        const clientId = req.headers['x-client-id'] as string || req.ip

        if (!checkRateLimit(clientId, limit)) {
            return res.status(429).json({
                error: 'Too Many Requests',
                retryAfter: 60
            })
        }

        next()
    }
}

// Usage
app.use('/api', rateLimitMiddleware(100))

With Monitoring

import { MemoryCache } from '@humanspeak/memory-cache'

const rateLimitCache = new MemoryCache<RateLimitEntry>({
    maxSize: 100000,
    ttl: 60 * 1000,
    hooks: {
        onSet: ({ key, value }) => {
            if (value.count === 1) {
                metrics.increment('ratelimit.new_client')
            }
        },
        onExpire: ({ key }) => {
            metrics.increment('ratelimit.window_reset')
        }
    }
})

function checkRateLimit(clientId: string, limit: number): boolean {
    const key = `ratelimit:${clientId}`
    const entry = rateLimitCache.get(key)

    if (!entry) {
        rateLimitCache.set(key, { count: 1, resetAt: Date.now() + 60000 })
        return true
    }

    if (entry.count >= limit) {
        metrics.increment('ratelimit.exceeded', { clientId })
        return false
    }

    rateLimitCache.set(key, {
        count: entry.count + 1,
        resetAt: entry.resetAt
    })

    return true
}
import { MemoryCache } from '@humanspeak/memory-cache'

const rateLimitCache = new MemoryCache<RateLimitEntry>({
    maxSize: 100000,
    ttl: 60 * 1000,
    hooks: {
        onSet: ({ key, value }) => {
            if (value.count === 1) {
                metrics.increment('ratelimit.new_client')
            }
        },
        onExpire: ({ key }) => {
            metrics.increment('ratelimit.window_reset')
        }
    }
})

function checkRateLimit(clientId: string, limit: number): boolean {
    const key = `ratelimit:${clientId}`
    const entry = rateLimitCache.get(key)

    if (!entry) {
        rateLimitCache.set(key, { count: 1, resetAt: Date.now() + 60000 })
        return true
    }

    if (entry.count >= limit) {
        metrics.increment('ratelimit.exceeded', { clientId })
        return false
    }

    rateLimitCache.set(key, {
        count: entry.count + 1,
        resetAt: entry.resetAt
    })

    return true
}

Key Considerations

  • Max Size: Plan for number of unique clients
  • TTL: Determines the rate limit window
  • Client ID: Use a reliable identifier (API key, IP, user ID)
  • Distributed Systems: This pattern is per-instance; use Redis for distributed rate limiting