Last active
August 7, 2024 11:04
-
-
Save perkinsjr/1601038bc3318ddf8e981f473329acc9 to your computer and use it in GitHub Desktop.
Using Clerk with Upstash for Middleware rate limiting and API Protection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getAuth, withClerkMiddleware } from "@clerk/nextjs/server"; | |
import { NextResponse, NextFetchEvent } from "next/server"; | |
import type { NextRequest } from "next/server"; | |
import { Ratelimit } from "@upstash/ratelimit"; | |
import { Redis } from "@upstash/redis"; | |
// Add public paths for Clerk to handle. | |
const publicPaths = ["/", "/sign-in*", "/sign-up*", "/api/blocked"]; | |
// set your rate limit. | |
const ratelimit = new Ratelimit({ | |
redis: Redis.fromEnv(), | |
limiter: Ratelimit.cachedFixedWindow(1, "10s"), | |
ephemeralCache: new Map(), | |
analytics: true, | |
}); | |
// This checks if the pathname you are is public | |
const isPublic = (path: string) => { | |
return publicPaths.find((x) => | |
path.match(new RegExp(`^${x}$`.replace("*$", "($|/|\\.)"))) | |
); | |
}; | |
// this checks if you are hitting an API | |
const isAPI = (path: string) => { | |
return path.match(new RegExp(`^\/api\/`)) | |
} | |
export default withClerkMiddleware(async (request: NextRequest, event: NextFetchEvent) => { | |
//Rate limit apis. | |
if (isAPI(request.nextUrl.pathname) && request.nextUrl.pathname !== '/api/blocked') { | |
const ip = request.ip; | |
const { success, pending, limit, reset, remaining } = await ratelimit.limit(`ratelimit_middleware_${ip}`); | |
event.waitUntil(pending); | |
const res = success ? NextResponse.next() : NextResponse.redirect(new URL("/api/blocked", request.url)); | |
res.headers.set("X-RateLimit-Limit", limit.toString()); | |
res.headers.set("X-RateLimit-Remaining", remaining.toString()); | |
res.headers.set("X-RateLimit-Reset", reset.toString()); | |
return res; | |
} | |
// do nothing | |
if (isPublic(request.nextUrl.pathname)) { | |
return NextResponse.next(); | |
} | |
// if the user is not signed in redirect them to the sign in page. | |
const { userId } = getAuth(request); | |
if (!userId) { | |
// redirect the users to /pages/sign-in/[[...index]].ts | |
const signInUrl = new URL("/sign-in", request.url); | |
signInUrl.searchParams.set("redirect_url", request.url); | |
return NextResponse.redirect(signInUrl); | |
} | |
return NextResponse.next(); | |
}); | |
// Stop Middleware running on static files | |
export const config = { | |
matcher: [ | |
/* | |
* Match all request paths except for the ones starting with: | |
* - _next | |
* - static (static files) | |
* - favicon.ico (favicon file) | |
*/ | |
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)", | |
"/" | |
], | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Probably not.
if I was rate limiting APIs today I’d use Unkey to handle it.
https://www.unkey.com/docs/quickstart/ratelimiting/nextjs
It’s cheaper and faster.