Implementing Robust API Rate Limiting in Node.js with express-rate-limit

4 min read

📚 Quick Review: This practical application is built upon a fundamental programming concept. Review the Theory Lesson here first.


Prerequisites

Before we dive into the implementation, ensure you have the following set up:

  • Node.js installed on your machine.
  • npm (Node Package Manager), which comes with Node.js.
  • A basic Express.js application. If you don’t have one, we’ll quickly set up a minimal example.

Installation

First, you need to install the necessary packages.

Step 1: Initialize Your Project (if not already done)

If you’re starting a new project, navigate to your project directory in the terminal and run:

npm init -y

This command creates a `package.json` file, which manages your project’s dependencies.

Step 2: Install Express and `express-rate-limit`

Now, install Express (if you don’t have it) and the `express-rate-limit` package:

npm install express express-rate-limit

The Rate Limiting Configuration Explained

Let’s look at the provided code snippet and break down each part of the `express-rate-limit` configuration. This code typically resides in a separate file, for example, `apiLimiter.js`.

const rateLimit = require('express-rate-limit');

// Configure request limits: Allow 100 requests per IP address every 15 minutes
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, 
  max: 100, 
  message: {
    status: 429,
    error: 'Too many requests from this IP, please try again after 15 minutes.'
  },
  standardHeaders: true, 
  legacyHeaders: false, 
});

// Apply protection to all API routes
// app.use('/api/', apiLimiter);

module.exports = apiLimiter;

Line-by-Line Breakdown:

  • `const rateLimit = require(‘express-rate-limit’);`: This line imports the `express-rate-limit` middleware into our Node.js application. It makes the rate limiting functionality available for use.
  • `const apiLimiter = rateLimit({…});`: Here, we are creating an instance of our rate limiter. The `rateLimit()` function is called with a configuration object that defines the rules for our API.
  • `windowMs: 15 * 60 * 1000,`: This crucial option defines the time window for which requests are counted. `15 * 60 * 1000` calculates to 15 minutes (15 minutes * 60 seconds/minute * 1000 milliseconds/second). All requests from a single IP within this 15-minute window will be tracked.
  • `max: 100,`: This sets the maximum number of requests allowed from a single IP address within the `windowMs` timeframe. In this case, an IP can make up to 100 requests every 15 minutes.
  • `message: {…},`: When an IP exceeds the `max` requests within the `windowMs`, this `message` object will be sent back as the response. It includes an HTTP `status` code of `429` (Too Many Requests) and a user-friendly `error` message.
  • `standardHeaders: true,`: This option enables the `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` headers in the response. These headers provide clients with information about their current rate limit status, helping them manage their requests.
  • `legacyHeaders: false,`: This disables the older `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. It’s generally recommended to use the `standardHeaders` for modern applications.
  • `// app.use(‘/api/’, apiLimiter);`: This commented-out line shows how you would apply the `apiLimiter` middleware to your Express application. By using `app.use(‘/api/’, apiLimiter);`, all routes starting with `/api/` would be protected by this rate limit. You can also apply it to specific routes or globally.
  • `module.exports = apiLimiter;`: This line exports the configured `apiLimiter` instance, making it available for other files in your Node.js project to import and use.

Integrating the Rate Limiter into an Express Application

Now, let’s see how to use this `apiLimiter` in a typical Express application. Create a file named `app.js` (or your main server file) and add the following code:

Example `app.js`

const express = require('express');
const apiLimiter = require('./apiLimiter'); // Assuming the above code is in apiLimiter.js

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

// Apply the rate limiter to all API routes starting with /api/
app.use('/api/', apiLimiter);

// A protected API endpoint
app.get('/api/data', (req, res) => {
  res.json({ message: 'This is your API data! You accessed a protected route.' });
});

// An unprotected route
app.get('/', (req, res) => {
  res.send('Welcome to the homepage. API is protected at /api/data');
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

To run this application, save both `apiLimiter.js` and `app.js` in the same directory, then open your terminal in that directory and execute:

node app.js

Testing Your Rate Limiter

Once your server is running, you can test the rate limit using tools like `curl` or Postman.

Using `curl` in your terminal:

# Make 100 requests (these should succeed)
for i in $(seq 1 100); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/data; done

# Make the 101st request (this should fail with 429)
curl -v http://localhost:3000/api/data

You should observe the first 100 requests returning a `200 OK` status, and the 101st request (and subsequent ones within the 15-minute window) returning a `429 Too Many Requests` status, along with your custom error message. Also, check the response headers for `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` to see the current status.

💡 Developer Tip: When testing rate limits, use tools like `curl` in a loop or a dedicated load testing tool. Pay close attention to the HTTP status codes and the `RateLimit-Remaining` header in the response. Remember that `express-rate-limit` defaults to an in-memory store, so restarting your server will reset the limits. For production, consider a persistent store like Redis (e.g., using `rateLimit({ store: new RedisStore(…) })`) to maintain limits across server restarts and multiple instances.

1 comment

Leave a Reply

Your email address will not be published. Required fields are marked *