@g14o/ratelimit
v0.1.0
Published
Framework-agnostic rate limiting with Upstash Redis.
Downloads
100
Maintainers
Readme
@g14o/ratelimit
Framework-agnostic rate limiting with Upstash Redis. Works with any runtime that uses Web Request / Response (Next.js App Router, Hono, Cloudflare Workers, etc.).
Install
pnpm add @g14o/ratelimit @upstash/redis @upstash/ratelimitPeers are optional in package.json metadata for metadata-only installs; add both Upstash packages when using Redis-backed limits in production.
Setup
Create an app-owned client in lib/rate-limit.ts:
import { createRateLimit } from "@g14o/ratelimit";
import { logger } from "@/lib/logger";
export const { withRateLimit, checkRateLimit, withUserRateLimit } =
createRateLimit({
redis: {
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
},
logger,
});Examples
Next.js App Router route
import { withRateLimit } from "@/lib/rate-limit";
export const POST = withRateLimit(
async (req) => Response.json({ ok: true }),
{ tier: "moderate" }
);Manual check (middleware or custom flow)
Use checkRateLimit when you are not wrapping a route handler:
import { checkRateLimit } from "@/lib/rate-limit";
export async function handleRequest(req: Request) {
const result = await checkRateLimit(req, { tier: "strict" });
if (!result.ok) {
return Response.json({ error: "Too many requests" }, { status: 429 });
}
// ...
}Per-user rate limit
import { withUserRateLimit } from "@/lib/rate-limit";
export const POST = withUserRateLimit(
async (req) => Response.json({ ok: true }),
async (req) => req.headers.get("x-user-id"),
{ tier: "auth" }
);Custom identifier
import { withRateLimit } from "@/lib/rate-limit";
export const GET = withRateLimit(
async (req) => Response.json({ ok: true }),
{
tier: "lenient",
identifierFn: async (req) =>
req.headers.get("x-api-key") ?? "anonymous",
}
);Hono
import { Hono } from "hono";
import { createRateLimit } from "@g14o/ratelimit";
const { withRateLimit } = createRateLimit({
redis: {
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
},
});
const app = new Hono();
app.get(
"/api",
withRateLimit(
async (req) =>
new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
}),
{ tier: "moderate" }
)
);Custom tiers
Override built-in tier limits when creating the client:
export const { withRateLimit } = createRateLimit({
redis: {
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
},
tiers: {
strict: { limit: 3, window: "30 s" },
auth: { limit: 10 },
},
});Build vs runtime
By default, inMemoryDuringBuild is true: during static build phases (Next.js sets NEXT_PHASE during next build / export), rate limiting uses an in-memory backend so prerender does not call Upstash. At runtime in production, Redis is used when configured.
import { createRateLimit } from "@g14o/ratelimit";
export const { withRateLimit } = createRateLimit({
redis: {
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
},
inMemoryDuringBuild: true, // default
});Import isBuildLikePhase() from @g14o/ratelimit/config if you need to detect build phase yourself.
See @g14o/core for the bundled package (Next-coupled rate limit on @g14o/core/ratelimit).
Import paths
| Use case | Import |
|----------|--------|
| Rate limit factory | import { createRateLimit } from "@g14o/ratelimit" |
| Redis / env helpers | import { createRedisClient, isBuildLikePhase } from "@g14o/ratelimit/config" |
Bundled alternative
pnpm add @g14o/core @upstash/redis @upstash/ratelimit nextImport rate limiting from @g14o/core/ratelimit (uses next/server types).
