Rate-limiting, the easy way

Demo

GitHub Repo

Implement rate limiting before you finish your coffee?

Rate-limiting is a crucial part of handling large swaths of traffic. Whether you want to protect your system from malicious actors or if you simply want to control usage, rate-limiting is a key way to ensure that usage doesn't spin out of control.

In this simple tutorial, we are going to have a home page that will check your IP, and determine if you should be rate-limited.

Set up Upstash

First things, first. We need to set up an Upstash Redis DB.

Next, let's install our primary Upstash packages.

yarn add @upstash/redis @upstash/ratelimit

Now, we're ready to go ahead and create our .env.local file to add our Upstash environment variables:

UPSTASH_REDIS_REST_URL="https://<YOUR_UPSTASH_URL>.upstash.io"
UPSTASH_REDIS_REST_TOKEN="<YOUR_UPSTASH_TOKEN>"

Make sure to key them UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN considering the following code explicitly looks for those keys.

Now, we want to create our rateLimiter - which you can find an example of here

import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

export const ratelimiter = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(4, '10 s'),
  analytics: true,
})

As you can see Redis.fromEnv() is what is looking for those explicit UPSTASH_* environment variables.

The key part of the RateLimiter is this line:

limiter: Ratelimit.slidingWindow(4, '10 s')

It says that it will only allow 4 requests in a 10 second window.

Now, we implement

As this use case is rather simple, it doesn't have many moving pieces. That being said, you should be able to easily tailor this to your requirements. For our example, we will simply limit in getServerSideProps on the home page, passing in the users IP address:

export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  try {
    const forwarded = req.headers['x-forwarded-for']

    const ip =
      typeof forwarded === 'string'
        ? forwarded.split(/, /)[0]
        : req.socket.remoteAddress

    const { success, remaining } = await ratelimiter.limit(ip!)

    return {
      props: {
        limited: !success,
        ip: ip,
        remaining,
      },
    }
  } catch (error) {
    return {
      props: {
        limited: false,
      },
    }
  }
}

They key here is the following:

const { success, remaining } = await ratelimiter.limit(ip!)

As mentioned before, for this demo we are passing in the users IP address. This could very easily be something else identifiable like their userId or email. The response object contains success, remaining, and other useful values. For this demo, we deconstruct and use only those two values and then pass them in as props to the page.

Voila!

That's it. You've add rate-limiting to your app. If success you can allow them "through", else, deny access!

Stay up to date

Get notified when I publish something new, and unsubscribe at any time.