Skip to content

Commit 1d65047

Browse files
committed
Add rate limiter middleware with Redis support
Introduced a new rate limiter middleware in TypeScript to control API request volume. The middleware supports both local Redis and Upstash configurations for flexible rate limiting. It ensures that requests exceeding the set limit return a 429 status code. Took 37 seconds
1 parent 5093ab6 commit 1d65047

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed

middleware/rateLimiter.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Redis } from '@upstash/redis'
2+
import { createClient, RedisClientType } from 'redis'
3+
import { NextResponse } from 'next/server'
4+
import type { NextRequest } from 'next/server'
5+
6+
let redisClient: Redis | RedisClientType | null = null
7+
8+
async function initializeRedisClient() {
9+
if (redisClient) return redisClient
10+
11+
const useLocalRedis = process.env.USE_LOCAL_REDIS === 'true'
12+
13+
if (useLocalRedis) {
14+
const localRedisUrl = process.env.LOCAL_REDIS_URL || 'redis://localhost:6379'
15+
redisClient = createClient({ url: localRedisUrl }) as RedisClientType
16+
await redisClient.connect()
17+
} else {
18+
const upstashRedisRestUrl = process.env.UPSTASH_REDIS_REST_URL
19+
const upstashRedisRestToken = process.env.UPSTASH_REDIS_REST_TOKEN
20+
21+
if (upstashRedisRestUrl && upstashRedisRestToken) {
22+
redisClient = new Redis({
23+
url: upstashRedisRestUrl,
24+
token: upstashRedisRestToken
25+
})
26+
}
27+
}
28+
29+
return redisClient
30+
}
31+
32+
export async function checkRateLimits(apiKey: string, limits: { perMinute: number }) {
33+
const client = await initializeRedisClient()
34+
if (!client) throw new Error('Redis client not initialized')
35+
36+
const now = Date.now()
37+
const windowKey = `ratelimit:${apiKey}:${Math.floor(now / 60000)}` // Key for current minute
38+
39+
let currentCount: number
40+
41+
if (client instanceof Redis) {
42+
currentCount = await client.incr(windowKey)
43+
await client.expire(windowKey, 60)
44+
} else {
45+
const multi = client.multi()
46+
const results = await multi
47+
.incr(windowKey)
48+
.expire(windowKey, 60)
49+
.exec()
50+
currentCount = (results?.[0] as number) || 0
51+
}
52+
53+
if (currentCount > limits.perMinute) {
54+
throw new Error(`Rate limit exceeded: ${limits.perMinute} requests per minute`)
55+
}
56+
57+
return currentCount
58+
}
59+
60+
export async function withRateLimit(
61+
request: NextRequest,
62+
handler: (request: NextRequest) => Promise<NextResponse>,
63+
limits: { perMinute: number }
64+
) {
65+
const apiKey = request.headers.get('x-api-key')
66+
if (!apiKey) {
67+
return NextResponse.json({ error: 'Missing API key' }, { status: 401 })
68+
}
69+
70+
try {
71+
await checkRateLimits(apiKey, limits)
72+
return await handler(request)
73+
} catch (error) {
74+
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 })
75+
}
76+
}

0 commit comments

Comments
 (0)