An alternative approach to building a simple API Rate limiter using NodeJS and Redis

Recently I came across an interesting problem of building an API rate limiter. While doing a bit of research on the same, I came across various algorithms and approaches. Some of the popular algorithms used to implement rate limiting are:

This post is NOT about these algorithms. I will try and explain an alternative approach here which makes use of certain Redis specific features.

Design/Algorithm

High level system setup:

High level system setup

Lets say we have some way of uniquely identifying the source of each request. This can be the ip address, API key, user token or even user name.

For this example lets assume this unique identifier is a user token and we want to set a rate limit of 20 requests per min for the user. Now for each user we maintain one key in our redis store.

Basic idea is whenever we receive a request:

SET abcdefghijklmno 0 EX 60 NX

This will allow us to set the key to 0 if it doesnt exist and auto expire/clean the key after 60 secs. Note this doesnt alter the key if it already exists.

INCR abcdefghijklmno

TLDR;

Redis commands:

> MULTI
OK
> SET <user_token> 0 EX 60 NX
QUEUED
> INCR <user_token>
QUEUED
> EXEC
1) OK
2) (integer) 1

If the value of the integer in the last result is greater than 20 we reject the request.

Show me some code

Lets take the case of a simple express server:

const app = express()
app.post('/login', loginHandler)
app.get('/ping', pingHandler)
app.get('/api-1', api1Handler)
app.post('/api-2', api2Handler)
app.get('/api-3', api3Handler)

To rate limit /api-1, /api-2 and /api-3 to 20 requests per min, create an express middleware: rateLimiter.js

const rateLimiter = (req, res, next) => {
  return next()
}

module.exports = { rateLimiter }

And add it before the endpoints to be rate limited:

const { rateLimiter } = require('./rateLimiter')

const app = express()
app.post('/login', loginHandler)
app.get('/ping', pingHandler)
app.use(rateLimiter) // middleware to limit requests to api-1, api-2, api-3
app.get('/api-1', api1Handler)
app.post('/api-2', api2Handler)
app.get('/api-3', api3Handler)

Now modify rateLimiter.js with the actual logic:

const redis = require('redis')

const client = redis.createClient(process.env.REDIS_URL)

client.on('error', err => console.log(`Error ${err}`))

const rateLimiter = (req, res, next) => {
  const token = req.user.token // unique identifier - can be ip, API_KEY, etc
  client
    .multi() // start a transaction
    .set([token, 0, 'EX', 60, 'NX']) // SET UUID 0 EX 60 NX
    .incr(token) // INCR UUID
    .exec((err, replies) => {
      if (err) {
        return res.status(500).send(err.message)
      }
      const reqCount = replies[1]
      if (reqCount > 20) {
        return res
          .status(403)
          .send(`Quota of ${20} per ${60}sec exceeded`)
      }
      return next()
    })
}

module.exports = { rateLimiter }

In ~20 lines of code we have our very own simple and performant rate limiter.

References

This post was ported over from Atul's old blog. Read the original here.