Skip to content

Instantly share code, notes, and snippets.

@ipenywis
Created September 2, 2024 16:26
Show Gist options
  • Save ipenywis/ac15003e62ab629b630bc1623f56e5d6 to your computer and use it in GitHub Desktop.
Save ipenywis/ac15003e62ab629b630bc1623f56e5d6 to your computer and use it in GitHub Desktop.
Add rate limit to your Next.js API route
import { LRUCache } from "lru-cache";
import { NextRequest, NextResponse } from "next/server";
import { getIpAddressFromRequest } from "./ip";
type Options = {
uniqueTokenPerInterval?: number;
interval?: number;
};
export enum RATE_LIMIT {
"RATE_LIMITED" = "RATE_LIMITED",
"PASS_THROUGH" = "PASS_THROUGH",
}
export interface RateLimitPromiseReturn {
message: RATE_LIMIT;
remaining: string;
limit: string;
}
export default function rateLimit(options?: Options) {
const tokenCache = new LRUCache({
max: options?.uniqueTokenPerInterval || 500,
ttl: options?.interval || 60000,
});
return {
/**
* By default it will use the request's ip address, otherwise, fallaback to provided token
* If rate limit is hit, the promise will reject with a ready-made Next response
* Otherwise, it will resolve with a RATE_LIMIT.PASS_THROUGH string
*/
check: (req: NextRequest, limit: number, token: string) =>
new Promise<RateLimitPromiseReturn>((resolve, reject) => {
const clientIpAddress = getIpAddressFromRequest(req);
console.info("Rate limiting client: ", clientIpAddress || token);
const tokenCount = (tokenCache.get(
clientIpAddress || token
) as number[]) || [0];
if (tokenCount[0] === 0) {
tokenCache.set(clientIpAddress || token, tokenCount);
}
tokenCount[0] += 1;
console.info(`Rate limiting tries: ${tokenCount} / ${limit}`);
const currentUsage = tokenCount[0];
const isRateLimited = currentUsage >= limit;
const remaining = String(isRateLimited ? 0 : limit - currentUsage);
if (isRateLimited) {
const rateLimitedResponse = NextResponse.json({
status: 429,
message: "API rate limit exceeded. Please try again later.",
details:
"You have exceeded the allowed number of requests. To ensure fair usage and maintain system stability, please wait before making additional requests.",
});
return reject(rateLimitedResponse);
} else
return resolve({
message: RATE_LIMIT.PASS_THROUGH,
remaining,
limit: String(limit),
});
}),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment