Introduction

Welcome to the very first lesson of the "Implementing Rate Limiting" course! 🚀

In this lesson, we will explore rate limiting, a crucial technique for enhancing the security of your API. Let's dive in! 🎉

What is Rate Limiting?

Rate limiting is a strategy that controls how many requests a client (identified by IP address, API key, or other identifiers) can make to your API within a specified time period. When the limit is reached, subsequent requests are blocked until the time period resets.

For example, you might configure your API to allow each client:

  • No more than 5 requests per minute
  • No more than 100 requests per hour
  • No more than 1000 requests per day
Why Implement Rate Limiting?

Rate limiting serves several important purposes:

  • Prevents server overload – Protects your server resources from being overwhelmed
  • Defends against DoS attacks – Makes it harder for attackers to flood your service
  • Ensures fair usage – Prevents a single client from monopolizing your API
  • Manages traffic spikes – Helps maintain consistent performance during high-traffic periods
  • Reduces costs – Limits resource consumption for services with usage-based pricing
Rate Limiters as Express Middleware

Rate limiters in Express work as standard middleware functions:

  1. The rateLimit() function creates a middleware that sits between client requests and your route handlers
  2. When requests arrive, this middleware checks if the client has exceeded their limit
  3. If the limit is exceeded, it responds with a 429 error before the request reaches your routes
  4. If within limits, it allows the request to continue to your route handlers

This middleware pattern lets rate limiters intercept and filter requests before they reach your application logic.

Current API Routes Setup

Let's look at a simplified version of our current route setup in src/server/routes/index.ts:

// src/server/routes/index.ts
import express from 'express';
import snippetsRouter from './snippets';
import authRouter from './auth';
import staticRouter from './static';

const router = express.Router();

router.use('/api/snippets', snippetsRouter);
router.use('/api/auth', authRouter);
router.use('/', staticRouter);

export default router;

Our snippet router includes a test endpoint we can use to demonstrate rate limiting:

// Simplified version of src/server/routes/snippets.ts
import express from 'express';

const router = express.Router();

// Test endpoint for rate limiting
router.get('/test-rate-limit', (req, res) => {
  res.json({ message: "This endpoint is just for testing rate limiting" });
});

// Other endpoints would be here

export default router;

Currently, there is no rate limiting applied to any of these endpoints, making our API vulnerable to request flooding.

Exploiting the Vulnerability

An attacker could target these endpoints with repeated calls:

# Simulate an attack using our test endpoint
for i in {1..100}; do
  curl http://localhost:3001/api/snippets/test-rate-limit
done

This could overwhelm our server, especially for resource-intensive operations.

Implementing a Global Rate Limiter, Step 1: Import the rate limiter package

To protect our API, we'll add rate limiting at the global level in our index.ts file.

First, we need to import the express-rate-limit package:

import rateLimit from 'express-rate-limit'
Step 2: Configure the rate limiter

Next, we'll create a rate limiter middleware with these important configuration options:

// Configure global API rate limiter middleware
const globalLimiter = rateLimit({
  windowMs: 30 * 1000,  // Time window for tracking requests (30 seconds)
  max: 5,              // Maximum number of requests allowed per client during the window
  message: 'Too many requests, please try again later.', // Response sent when a client exceeds the limit
  standardHeaders: true,  // Return rate limit info in the `RateLimit-*` headers
  legacyHeaders: false,   // Disable the `X-RateLimit-*` headers
  skipSuccessfulRequests: false // Count successful requests against the rate limit
});
Step 3: Apply the rate limiter to all API routes

Now, we'll apply the rate limiter middleware to all API routes using Express's standard middleware pattern:

// Apply the rate limiter middleware to all API routes
router.use('/api', globalLimiter);

// Mount other routers
router.use('/api/snippets', snippetsRouter);
router.use('/api/auth', authRouter);
router.use('/', staticRouter);

By applying the middleware to the /api path, all routes under this path will be protected. The rate limiter will process requests before they reach the route handlers in your other routers.

Testing the Rate Limiter

We can test our rate limiter implementation using this test script:

// testGlobalLimit.js
const axios = require('axios');

(async () => {
  console.log("Testing global rate limit...");
  
  console.log("\nTesting rate limit with test endpoint:");
  for (let i = 1; i <= 10; i++) {
    try {
      const resp = await axios.get('http://localhost:3001/api/snippets/test-rate-limit');
      console.log(`Request ${i}:`, resp.status, "Success");
    } catch (err) {
      if (err.response) {
        console.log(`Request ${i}:`, err.response.status, err.response.data);
      } else {
        console.error(`Request ${i} failed:`, err.message);
      }
    }
    
    // Add a small delay between requests
    await new Promise(resolve => setTimeout(resolve, 10));
  }
})();

When you run this script, you'll see output similar to this:

Testing global rate limit...

Testing rate limit with test endpoint:
Request 1: 200 Success
Request 2: 200 Success
Request 3: 200 Success
Request 4: 200 Success
Request 5: 200 Success
Request 6: 429 Too many requests, please try again later.
Request 7: 429 Too many requests, please try again later.
Request 8: 429 Too many requests, please try again later.
Request 9: 429 Too many requests, please try again later.
Request 10: 429 Too many requests, please try again later.

This confirms our rate limiter is working properly - allowing the first 5 requests and blocking subsequent ones.

Note: Your actual output may vary slightly depending on timing factors such as system delays or longer gaps between requests. The key pattern to observe is that after approximately 5 requests, you should start seeing 429 responses.

Conclusion and Next Steps

In this lesson, we learned how to implement a global rate limiter to protect all of our API routes at once. This approach provides a baseline level of protection against request flooding.

By adding just a few lines of code to our main router, we've significantly improved the security of our application against potential DoS attacks.

In future lessons, we'll explore more advanced rate limiting strategies, such as:

  • Setting different limits for different routes
  • Creating specialized limiters for authentication endpoints
  • Implementing more sophisticated rate-limiting algorithms

Remember that rate limiting is just one aspect of API security, but it's an essential first step in building a robust, secure application.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal