Profile Photo

Rate limiting in nextjs using Redis

Created on: Aug 13, 2024

Rate limiting

Let's understand the concept by creating a nextjs app.

npx create-next-app@latest ratelimiting cd ratelimiting

After this create directory pages/api to write a sample rest api. under this folder create a file called hello.ts with below code

import type { NextApiRequest, NextApiResponse } from "next"; type ResponseData = { message: string; }; export default function handler( req: NextApiRequest, res: NextApiResponse<ResponseData> ) { if (req.method === "GET") { res.status(200).json({ message: "Hello world" }); } }

Now let's run out app and hit api multiple times say 50 times.

seq 1 50 | xargs -n1 -P50 -I{} curl -s -o /dev/null -w "Request {}: %{http_code}\n" http://localhost:3000/api/hello

Screenshot of hitting 50 times

If such request comes in number like more number, it can prevent legitimate request from some genuine client. One of the approach to prevent this is by using rate limiting.

Rate limiting is a technique used to control the number of requests a user or system can make to a server within a specific time period. It helps prevent abuse, protect resources, and ensure fair usage by restricting excessive or malicious traffic.

In nextjs we will use Vercel Edge Middleware middleware of nextjs to apply rate limiting.

Before going ahead we have to create a account on upstash. It is pretty simple. Then under Redis tab, create a redis database. upstash

Then click on eye option to revel secrets. copy url and token and add in .env file.(create a .env file first)

VERSEL_REDIS_URL=<url> VERSEL_REDIS_TOKEN=<token>

upstash redis

Now let's write a middleware which will allow five connection in 20 seconds for a ip address.

import { ipAddress, next } from "@vercel/edge"; import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis"; const redis = new Redis({ url: process.env.VERSEL_REDIS_URL, token: process.env.VERSEL_REDIS_TOKEN, }); const ratelimit = new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(5, "20 s"), }); export const config = { matcher: "/api/hello", }; export default async function middleware(request: Request) { const ip = ipAddress(request) || "127.0.0.1"; console.log("middleware is called"); const { success, pending, limit, reset, remaining } = await ratelimit.limit( ip ); return success ? next() : Response.redirect(new URL("/blocked.html", request.url)); }

Let's call api ten times and check. we will get 200 for only 5 request. for other we will get 302. calling api ten times

You can check whole code in github