Next.js : Add rate limiting to your API using Upstash

A comprehensive guide on how you can add rate limiting to your Next.js API using Upstash.

· 5 min read
Nextjs & upstashlgoos
Nextjs rate limiting with upstash

After reading this you be able to implement rate limiting in your Next.js API using Upstash, a serverless database service for Redis. Rate limiting is a technique to prevent excessive requests from overloading your server or API. It can also help you avoid spam, abuse, and denial-of-service attacks.

What is rate limiting and why do i need it ?

Request limiting is a technique that helps you improve your API's performance, availability, security, and cost-efficiency. By setting a maximum number of requests that a user can make in a given time period, you can prevent your API from being overloaded by excessive or malicious traffic. This way, you can ensure that your API can serve all legitimate users with optimal speed and reliability, reduce the risk of denial-of-service (DoS) attacks, and control the usage and consumption of your API resources.

Why Upstash ?

Upstash is a serverless Redis service that provides low latency and high availability for your applications. It is especially useful for rate limiting and its easy to set up.

Upstash also has a pay-as-you-go pricing model, which means you only pay for what you use, also you can register for a free tier account with up to 10K commands per day and total 256Mb data size.

Step 1 : Setting up the Next.js project

We can start by creating our new Next.js project by running the command:

npx create-next-app@latest

I will be using the app router but this should work in pages folder just fine because we will be using Next.js middleware, and this is how my terminal look.

Console preview after running the create project command

Your project directory should look something like this:

└── node_modules
└── public
└── src
│   └── app

We will create a new file in ratelimit-tutorial/src  called  middleware.ts. So your project should look like this:

└── node_modules
└── public
└── src
│   └── app
│   ├── middleware.ts

We will also create a simple API endpoint by creating a new route.ts file in ratelimit-tutorial/src/app/api  like so:

└── node_modules
└── public
└── src
│   └── app
│   │   └── api
│   │   │   ├── route.ts
│   ├── middleware.ts

And this is what route.ts should look like:

import { NextResponse } from "next/server";

export async function GET(request: Request) {
  return NextResponse.json({message: "Hello World from our API endpoint"});
}

Now if we start the development server by running:

npm run dev

Then go to localhost:3000/api you should be able to see this:

API endpoint response preview

For now this should be enough for our project setup.

Step 2 : Creating an Upstash account

Head to Upstash website and create a new account by using your  Github account or whatever suits you. Logging to your account and head to Upstash Dashboard :

Upstash Dashboard preview

Select Redis on top and create a new database by pressing the button on the bottom right corner, you can name it whatever you want and after its created you should have it under Databases like so :

Upstash Dashboard Database

Step 3 : Implementing the rate limiting

Now we can finally start writing code but first we need to grab two important  variables from Upstash by following these steps:

  1. Click on your database that you just created.
  2. Scroll down until you find the 'REST API' panel.
  3. Click on .env to reveal your database url and secret token.
  4. Copy the variables by pressing the button on the right side.
Upstash Database REST API panel

Now we go back to our project folder and create a new file .env.local in the root directory and paste the two variables. It should look something like this:

UPSTASH_REDIS_REST_URL="YOUR-UPSTASH-URL"
UPSTASH_REDIS_REST_TOKEN="YOUR-UPSTASH-TOKEN"

DO NOT share these variables with anyone or they will be able to use your database.

Our project should look like this by now:

└── node_modules
└── public
└── src
│   └── app
│   │   └── api
│   │   │   ├── route.ts
│   ├── middleware.ts
├── .env.local

We need to install two Upstash libraries that we shall be using to make our life easier and not have to worry about writing any complicated code.

First navigate to the project directory:

cd ratelimit-tutorial

Then we run the following command to install the required libraries:

npm install @upstash/ratelimit @upstash/redis

Now we head back to middleware.ts that we created at the start and paste the following code:

import { NextRequest, NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const upstashRedisUrl = process.env.UPSTASH_REDIS_REST_URL;
const upstashRedisToken = process.env.UPSTASH_REDIS_REST_TOKEN;
if (!upstashRedisUrl || !upstashRedisToken) {
  throw new Error("Missing Upstash Redis variables in .env.local file");
}

const redisClient = new Redis({
  url: upstashRedisUrl,
  token: upstashRedisToken,
});

const maxAllowedRequests = 3;
const requestsDurationWindow = "1 m";

const rateLimiter = new Ratelimit({
  redis: redisClient,
  limiter: Ratelimit.slidingWindow(maxAllowedRequests, requestsDurationWindow),
});

const getClientIp = (request: NextRequest) => {
  let ip = request.ip ?? request.headers.get("x-real-ip");
  const forwardedFor = request.headers.get("x-forwarded-for");
  if (!ip && forwardedFor) {
    ip = forwardedFor.split(",").at(0) ?? null;
    return ip;
  }
  return ip;
};

export async function middleware(request: NextRequest) {
  const identifier = getClientIp(request);

  if (identifier) {
    const result = await rateLimiter.limit(identifier);
    if (!result.success) {
      return NextResponse.json(
        { error: `Too many requests, please try again later` },
        { status: 429 }
      );
    }
  }
}

// Match All paths starting with /api
export const config = {
  matcher: ["/api/:path*"],
};

And that's it !! your Upstash rate limiter is working. if we run the development server again:

npm run dev

And visit localhost:3000/api . If we try to spam refresh by pressing F5 we will get this response:

API rate limited response

But you might ask, what is it doing exactly?

Our middleware will intercept requests to all paths starting with /api then we get the client IP address from the request using the function getClientIp and use it as an identifier. We send a request to Upstash with the identifier to check if the client has sent more than 3 requests in the past minute, if so the request is rejected and we return 429 TOO MANY REQUESTS.

Note that the identifier can be anything you want, for example you can use the user ID if you have authentication in your Next.js app.

How can I change the limitations?

You can choose how strict you want the limitation to be in the rate limiter:

const maxAllowedRequests = 3;
const requestsDurationWindow = "1 m";

maxAllowedRequests : determines how many requests are allowed.
requestsDurationWindow : is the window duration between these requests.
In the above example we are allowing 3 requests per minute.

You can check the full project code on Github.

If you would like to do more complicated stuff with Upstash you can visit their documentation Upstash Documentation.