ratelite
Version:
A simple, modern, and flexible Redis-based rate limiter for Express.js — built for real-world APIs, SaaS, and edge-compatible environments.
79 lines • 3.77 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Redis } from "@upstash/redis";
/**
* Creates an Express-compatible rate limiter middleware using Upstash Redis.
*
* @param options Configuration for rate limiting behavior.
*
* @param options.redisUrl - (Required) The REST URL of your Upstash Redis database.
* @param options.redisToken - (Required) The access token for Upstash Redis.
* @param options.maxRequests - (Required) The maximum number of requests allowed per time window.
* @param options.windowInSeconds - (Required) The duration of the rate-limiting window, in seconds.
* @param options.keyFn - (Optional) A custom function to generate a unique key per requester (default: `req.ip`).
* @param options.onLimitReached - (Optional) A callback that runs when the rate limit is exceeded.
* @param options.onError - (Optional) A function to run when Redis fails (e.g., for logging).
* @param options.debug - (Optional) If true, logs key usage info for debugging.
*
* @returns Express middleware function that enforces the rate limit.
*/
export function createRateLimiter(options) {
if (!options.redisUrl || !options.redisToken) {
throw new Error("Redis URL and token are required in RateLimiter options.");
}
const redis = new Redis({
url: options.redisUrl,
token: options.redisToken,
});
return function rateLimiter(req, res, next) {
return __awaiter(this, void 0, void 0, function* () {
try {
const rawKey = options.keyFn ? options.keyFn(req) : req.ip;
const key = `rate:${rawKey}`;
const count = yield redis.incr(key);
if (count === 1) {
yield redis.expire(key, options.windowInSeconds);
}
if (count > options.maxRequests) {
const ttl = yield redis.ttl(key);
res.set("Retry-After", ttl.toString());
if (options.onLimitReached) {
return options.onLimitReached(req, res);
}
return res.status(429).json({
status: 429,
success: false,
message: "Too many requests. Please try again later.",
retryAfter: ttl,
});
}
if (options.debug) {
console.log(`[Limiter] ${key} -> ${count}/${options.maxRequests}`);
}
next();
}
catch (error) {
if (options.debug) {
console.error(`[Limiter] Redis error:`, error);
}
if (options.onError) {
options.onError(error, req, res);
return;
}
return res.status(500).json({
status: 500,
success: false,
message: "Rate limiting failed. Please try again later.",
});
}
});
};
}
//# sourceMappingURL=limiter.js.map