Skip to content

Lightweight open-source rate-limiting middleware for Node.js/Express & Redis. Provides configurable limits per IP or user, integrates easily with GraphQL or REST-APIs, and helps protect backend routes from abuse and overload.

Notifications You must be signed in to change notification settings

Saif-Mohammed1/rate-limit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”’ Redis Rate Limiter with Upstash

A simple and efficient rate limiting utility using @upstash/redis and Lua scripting for atomicity. Designed for serverless and scalable applications.


πŸ“¦ Features

  • ⚑ Lightning-fast using Upstash Redis (HTTP-based)
  • βœ… Atomic rate limiting using Lua scripts
  • 🧠 Tracks request count per user/IP
  • 🚫 Blocks requests once the limit is reached
  • πŸ” Automatically resets after the time window

πŸ“ Project Structure

β”œβ”€β”€ lib/
β”‚   └── redis.ts             # Redis client setup
β”œβ”€β”€ utils/
β”‚   └── rateLimiter.ts       # Rate limiter logic
β”œβ”€β”€ .env.local               # Environment variables
β”œβ”€β”€ README.md

πŸš€ Getting Started

1. Install Dependencies

npm install @upstash/redis
# or
yarn add @upstash/redis

2. Setup .env.local

Create a .env.local file at the root of your project and add your Upstash Redis credentials:

UPSTASH_REDIS_REST_URL=https://your-upstash-url.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-secret-token

You can find these in your Upstash Console.


3. Redis Client Setup (lib/redis.ts)

import { Redis } from "@upstash/redis";

function getEnvOrThrow() {
  const url = process.env.UPSTASH_REDIS_REST_URL;
  const token = process.env.UPSTASH_REDIS_REST_TOKEN;

  if (!url || !token) {
    throw new Error(
      "❌ Missing Upstash Redis credentials. Please provide UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in your environment variables."
    );
  }

  return { url, token };
}

export const redis = new Redis(getEnvOrThrow());

4. Rate Limiter Logic (utils/rateLimiter.ts)

import { redis } from "@/lib/redis";

const WINDOW_IN_SECONDS = 60; // 1 minute
const MAX_REQUESTS = 30; // max requests per IP per minute
const KEY_PREFIX = "rate_limit:";

// Lua script to ensure atomic operations in Redis
const RATE_LIMIT_SCRIPT = `
local key = KEYS[1]
local window = tonumber(ARGV[1])

local current = redis.call('INCR', key)
if current == 1 then
  redis.call('EXPIRE', key, window)
end
local ttl = redis.call('TTL', key)
return { current, ttl }
`;

export const rateLimiter = {
  limit: async (identifier: string) => {
    const key = `${KEY_PREFIX}${identifier}`;

    try {
      const [currentRequests, ttl] = (await redis.eval(
        RATE_LIMIT_SCRIPT,
        [key],
        [WINDOW_IN_SECONDS.toString()]
      )) as [number, number];

      const remaining = Math.max(0, MAX_REQUESTS - currentRequests);

      const response = {
        success: true,
        allowed: currentRequests <= MAX_REQUESTS,
        limit: MAX_REQUESTS,
        remaining,
        reset: ttl,
        retryAfter: ttl,
        window: WINDOW_IN_SECONDS,
      };

      if (!response.allowed) {
        response.success = false;
        response.remaining = 0;
      }

      return response;
    } catch (error) {
      console.error("Rate limiter error:", error);
      return {
        success: true,
        allowed: true,
        limit: MAX_REQUESTS,
        remaining: MAX_REQUESTS,
        reset: WINDOW_IN_SECONDS,
        retryAfter: 0,
        window: WINDOW_IN_SECONDS,
      };
    }
  },
};

πŸ§ͺ Example Usage

In an API route or middleware:

import { rateLimiter } from "@/utils/rateLimiter";
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const ip =
    req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown";

  const result = await rateLimiter.limit(ip.toString());

  if (!result.allowed) {
    return res.status(429).json({
      message: "Too many requests. Please try again later.",
      retryAfter: result.retryAfter,
    });
  }

  // Proceed with your logic
  res.status(200).json({ message: "OK" });
}

πŸ§ͺ Another Example Usage For Next.js

In a middleware:

import { rateLimiter } from "@/utils/rateLimiter";
import { type NextRequest, NextResponse } from "next/server";
import { ipAddress } from "@vercel/functions";

export default async function middleware(req: NextRequest) {
  const response = NextResponse.next();

  const clientIp =
    req.headers.get("x-forwarded-for") ||
    ipAddress(req) ||
    req.headers.get("x-real-ip") ||
    "127.0.0.1";
  response.headers.s;
  if (pathname.startsWith("/api")) {
    const limit = await rateLimiter.limit(clientIp);

    if (!limit.allowed) {
      response.headers.set("X-RateLimit-Limit", limit.limit.toString());
      response.headers.set("X-RateLimit-Remaining", limit.remaining.toString());
      response.headers.set("X-RateLimit-Reset", limit.reset.toString());
      response.headers.set("Retry-After", limit.retryAfter.toString());
      //  return res.status(429).json({
      //    error: `Too many requests. Retry after ${limit.retryAfter} seconds`,
      //  });
      throw new AppError(tooManyRequestsTranslate[lang].title, 429);

      // NextResponse.redirect(new URL(CUSTOM_ERROR_PATH, req.url));
    }

    // Apply rate limit headers to all responses

    response.headers.set("X-RateLimit-Limit", limit.limit.toString());
    response.headers.set("X-RateLimit-Remaining", limit.remaining.toString());
    response.headers.set("X-RateLimit-Reset", limit.reset.toString());
  }
}

πŸ›‘οΈ Why Lua Script?

Using a Lua script in Redis ensures atomic operations, meaning no two requests can modify the rate limit count at the same timeβ€”ensuring accuracy.


πŸ’‘ Use Cases

  • Protect login routes
  • Throttle email/password reset forms
  • Rate limit API access per user/IP/device

πŸ“Œ Tips

  • You can adjust the rate window and limit by changing WINDOW_IN_SECONDS and MAX_REQUESTS.
  • You can use session tokens, API keys, or IPs as identifier.

🧠 Credits


About

Lightweight open-source rate-limiting middleware for Node.js/Express & Redis. Provides configurable limits per IP or user, integrates easily with GraphQL or REST-APIs, and helps protect backend routes from abuse and overload.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published