Rate limiting is an important feature that you need in production applications. It may sound scary, but it's actually fairly easy to implement API rate limiting in today's world. In this blog post, we'll walk through the process of implementing rate limiting using Unkey.
In this example we are using my t3-clerk-minimal app that you can find here. Make sure you have your Clerk keys in your application before starting.
What is Unkey?
Unkey an open source API development platform, and it has a really good free tier. We're going to use Unkey to implement rate limiting because they have a rate limit package that makes it dead simple to implement anywhere that you're using APIs.
First, you need to sign up for an account. Once you've done that, you'll need to click on your settings and create a root key, with the following permissions:- limit Then create your namespace by selecting ratelimit and "Create namespace", let us call it "ratelimit-demo".
Setting up the ENV
Now you have a root key add that to your .env
UNKEY_ROOT_KEY="the_root_key"
Using Clerk for User Authentication
If you haven't used Clerk before, it's a user authentication and management system for the modern web, supporting frameworks like Next.js, Remix, and Gatsby. In this example, we'll be using Clerk for user authentication and protected routes.
To implement rate limiting, we need an identifier for the user (e.g. user ID, IP address) to determine if the same user is making multiple requests. We'll use the user ID from Clerk for this purpose.
Creating the Rate Limiter
First, we need to install a single package, @unkey/ratelimit
. You can do so by running:
npm install @unkey/ratelimit
Next, import the package in your example router and initialize the rate limiter:
import { Ratelimit } from "@unkey/ratelimit";
const rateLimiter = new UnkeyRatelimit({
namespace: "ratelimit-demo",
rootKey: env().RATELIMIT_DEMO_ROOT_KEY,
limit: 2,
duration: 3000,
});
This example sets a rate limit of 2 requests per 3 seconds. You can adjust the values as needed.
Implementing the Rate Limiter in a Protected Route
Now, we will implement the rate limiter in a protected route. First, create your route (in this example, we'll call it expensive
). Inside the route, use the rate limiter with the user ID from Clerk:
import { TrpcError } from '@trpc/server';
// other routes
expensive: protectedProcedure.query(async ({ ctx }) => {
const { success } = await rateLimiter.limit(ctx.auth.userId);
if (!success) {
throw new TRPCError({ code: "TOO_MANY_REQUESTS" })
}
return "expensive"
})
This code checks whether the rate limit has been exceeded; if it has, a "Too Many Requests" error is thrown.
Completed code
import { z } from "zod";
import { Ratelimit } from "@unkey/ratelimit";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { TRPCError } from "@trpc/server";
const rateLimiter = new UnkeyRatelimit({
namespace: "ratelimit-demo",
rootKey: env().RATELIMIT_DEMO_ROOT_KEY,
limit: 2,
duration: 3000,
});
export const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
getAll: publicProcedure.query(({ ctx }) => {
return ctx.prisma.example.findMany();
}),
expensive: protectedProcedure.query(async ({ ctx }) => {
const { success } = await rateLimiter.limit(ctx.auth.userId);
if (!success) {
throw new TRPCError({ code: "TOO_MANY_REQUESTS" })
}
return "expensive"
})
});
Testing the Rate Limiter
Now that the rate limiter has been implemented in your application, you can test your protected route to ensure the rate limiting is working as intended. In this example, we will test the rate limiter using a "click here to protected procedure" button on a web page.
Run your application with npm run dev
, and navigate to your localhost URL. Click the button, sign in using your preferred authentication method, and you should be redirected to the protected page. Open your browser console and refresh the page - you should encounter a "Too Many Requests" error if you refresh too often.
If you wait a few seconds and refresh again, the error should be gone, indicating that the rate limiting is working as intended.
Conclusion
In this blog post, we've demonstrated how to implement rate limiting using Unkey, as well as integrating the rate limiter with Clerk for user authentication. Implementing rate limiting is an important aspect of maintaining a secure and stable production application, and this guide should help you get started in adding it to your own projects.
If you found this guide helpful, please consider subscribing to my Youtube channel or the newsletter for more tips and tutorials for web development. Happy coding!