Building Rate Limiter based on IP Address with Netlify Blobs and Edge Functions
Tags: webdev, javascript, programming, tutorial
Published: 2023-11-15 21:12:40

In this guide, we'll explore a streamlined approach to rate limit users using recently released [Netlify Blobs](https://www.netlify.com/blog/introducing-netlify-blobs-beta/). By the end, you'll have an edge function that'd rate limit users based on their IP Address.

## Setup a Node.js Project

Let's start by creating a directory and initializing a Node.js project:

```bash
mkdir rate-limiter
npm init -y
```

## Install Netlify Blobs

Install `Netlify Blobs` package to manage data storage of usage per ip address to rate limit the users with.

```bash
npm i @netlify/blobs
```

## Setup Netlify Edge Functions

Get started by creating Netlify Edge Functions by creating the default `netlify/edge-functions` directory with a `index.js` file as one of the edge function(s).

```bash
mkdir netlify/edge-functions
touch index.js
```

## Setup Netlify Configuration

Create a `netlify.toml` at the root of your project with the following content:

```toml
[[edge_functions]]
  function = "index"
  path = "/*"

[build]
  publish="."
```

The code above makes sure that `index.js` is used to serve incoming requests for all paths on your deployment.

## Set Rate Limiting Config

In the newly created `index.js`, set the maximum number of reuqests (here, `maxUpdates`) in the set interval (here, `timeSpan`) that do not exceed the rate limits.

```javascript
// File: index.js

const rateLimitConfig = { maxUpdates: 3, timeSpan: 86400 }

export default async (_, context) => {
    // Rate limiting logic here
}
```

## Create Unique User Identifier

- Retrieve the user's IP address from the Netlify Edge Functions context object

- Use the IP address as a unique identifier for rate limiting via creating the `identifier`

- Load the store (here, named `visits`) that keeps the collection of all the unique idenitifers

- Try to look for an existing record for the particular identifier

```javascript
// File: index.js
import { getStore } from '@netlify/blobs'

export default async (_, context) => {
  // Create a unique identifier based on user's IP
  const identifier = 'limiter_' + context.ip
  // Get the visits store
  const visits = getStore('visits')
  const t = await visits.get(identifier)
  // If a rate limiting key is found
  if (t) {
     // Continue Rate limiting logic here
  }
  else {
     // Continue Rate limiting logic here
  }
}
```

## Flow of Rate Limiting based on the Stored Value of User Usage Record

If a rate limiting key is found for the user's IP:
- Retrieve the rate limit data for the key
- Calculate the time difference since the last update
- Reset the count if the time window has passed
- Check if the request count is below the limit
- Increment the count if within the limit
- Return appropriate responses based on the conditions

```javascript
// File: index.js

// If a rate limiting key is found
if (t) {
  // Get rate limit data for the key
  const { count_updates, last_update } = JSON.parse(t)
  // Calculate time difference in seconds
  const currentTime = new Date().getTime()
  const timeDifference = Math.floor((currentTime - new Date(last_update).getTime()) / 1000)
  // If time window has passed, reset count
  if (timeDifference >= rateLimitConfig.timeSpan) {
    await visits.set(identifier, JSON.stringify({ count_updates: 1, last_update: currentTime }))
    // Continue with your processing
    return new Response(`Reload 2 more times to exceed the rate limit.`)
  }
  // Check if the request count is below the limit
  if (count_updates < rateLimitConfig.maxUpdates) {
    // Increment the number of updates in the store
    await visits.set(identifier, JSON.stringify({ count_updates: count_updates + 1, last_update: new Date().getTime() }))
    // Continue with your processing
    return new Response(`Reload ${rateLimitConfig.maxUpdates - count_updates} more time(s) to exceed the rate limit.`)
  } else {
    // If the limits equal or exceeds, return with a rate limit exceeded message
    return new Response(`Rate limit exceeded.`)
  }
}
else {
  // If a key is not found, set the key with a single update and continue
}
```

If no rate limiting key is found:
- Sets a new key with a single update for the user

```javascript
// File: index.js

// If a rate limiting key is found
if (t) {
    // Continue Rate limiting logic here
}
else {
  // If a key is not found, set the key with a single update and continue
  await visits.set(identifier, JSON.stringify({ count_updates: 1, last_update: new Date().getTime() }))
  return new Response(`Reload 2 more times to exceed the rate limit.`)
}
```

## Deployment and Live Demo

All done, let's deploy our rate limiter now!

### Deploying to Netlify

```bash
# Deploy to Netlify
netlify deploy --prod
```

### Live Demo

Explore the live example at [tryblobs.netlify.app](https://tryblobs.netlify.app/), and witness the Rate Limiter in action.