How to Implement Rate Limiting in JavaScript Apps?

Content verified by Anycode AI
July 21, 2024
Rate limiting is a very important technique in modern web apps. This way, it provides for a flow of incoming requests, which makes it regulated and offers protection against abuse. It tries to maintain the stability of the system, prevents exhaustion of resources, and spans usage among users in a fair manner. In the absence of properly designed rate limiting, an application might be liable to the following different problems: 1\. Denial of Service (DoS) attacks 2\. API abuses 3\. Performance degradation 4\. Increased infrastructure cost 5\. Unfair resource allocation The following guide shows exactly how to implement such a simple effective rate limiting solution in JavaScript. We shall run through a fixed window algorithm, applicable to server-side applications like Node.js, and even client-side applications. This guide describes how developers can indirectly enhance the resiliency of an application by strengthening its overall performance, therefore making the user experience more equitable.

Rate Limiting in JavaScript

Rate limiting is so important! It's a great way to stop people from overloading your server or API by capping the number of requests they can make in a certain timeframe. It keeps things fair and stable. Let's dive into how to do this in JavaScript. We’ll cover both client-side and server-side techniques.

Client-Side Rate Limiting

First up, client-side rate limiting. You can pull this off with throttling or debouncing.

Throttling: This takes care of ensuring a function gets called at most once during a specific timeframe.

Debouncing: Here, a function is called only after a certain period of silence.

Example: Throttle Function

Throttling is a lifesaver when you want to limit how frequently an action occurs. Here's an example:

function throttle(fn, wait) {
    let inThrottle, lastFn, lastTime;
    return function() {
        const context = this;
        const args = arguments;
        if (!inThrottle) {
            fn.apply(context, args);
            lastTime = Date.now();
            inThrottle = true;
        } else {
            clearTimeout(lastFn);
            lastFn = setTimeout(function() {
                if ((Date.now() - lastTime) >= wait) {
                    fn.apply(context, args);
                    lastTime = Date.now();
                }
            }, Math.max(wait - (Date.now() - lastTime), 0));
        }
    };
}

// Usage example:
const handleScroll = throttle(() => {
    console.log('Scroll event handler call');
}, 1000);

window.addEventListener('scroll', handleScroll);

Example: Debounce Function

Debouncing comes in handy when you want to perform an action after a period of inactivity. Here's how you can do it:

function debounce(fn, delay) {
    let timeoutId;
    return function() {
        const context = this;
        const args = arguments;
        clearTimeout(timeoutId);
        timeoutId = setTimeout(function() {
            fn.apply(context, args);
        }, delay);
    };
}

// Usage example:
const handleInput = debounce(() => {
    console.log('Input event handler call');
}, 1000);

const inputElement = document.querySelector('input');
inputElement.addEventListener('input', handleInput);

Server-Side Rate Limiting

Server-side rate limiting is all about using middleware to monitor and limit requests.

Using express-rate-limit

One popular solution is using the express-rate-limit package. It’s highly recommended for Express.js apps.

Install the package:

npm install express-rate-limit

Set up the middleware:

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

const app = express();

// Set the rate limiting rule
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // Limit each IP to 100 requests per windowMs
    message: "Too many requests from this IP, please try again after 15 minutes"
});

// Apply it to all requests
app.use(limiter);

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

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

Custom Middleware for Rate Limiting

If you're feeling adventurous or need something specific, you can roll out your own custom middleware.

Example: Custom Middleware

Set up a custom middleware:

const express = require('express');
const app = express();

const rateLimitMap = new Map();

const customRateLimiter = (req, res, next) => {
    const ip = req.ip;
    const currentTime = Date.now();
    const windowSize = 15 * 60 * 1000; // 15 minutes
    const requestLimit = 100;

    if (rateLimitMap.has(ip)) {
        const userRecord = rateLimitMap.get(ip);
        const requestsWithinWindow = userRecord.filter(requestTime => currentTime - requestTime < windowSize);

        if (requestsWithinWindow.length >= requestLimit) {
            res.status(429).json({ message: "Too many requests, please try again later." });
            return;
        }

        requestsWithinWindow.push(currentTime);
        rateLimitMap.set(ip, requestsWithinWindow);
    } else {
        rateLimitMap.set(ip, [currentTime]);
    }
    next();
};

// Apply the custom middleware
app.use(customRateLimiter);

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

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

How it works:

This custom middleware uses a map (rateLimitMap) to track IP addresses and their request times. Here’s how it operates:

  • It grabs the IP address and the current time.
  • If the IP is already on the map, it filters out any requests older than windowSize (15 minutes).
  • If the filtered requests within this timeframe hit the limit (requestLimit), it sends a 429 Too Many Requests status.
  • If not, it adds the current request timestamp to the array.

By tapping into these nifty techniques, be it on the client or server side, you can effectively limit rates in your JavaScript apps to prevent misuse and ensure everything runs smoothly.

Have any questions?
Our CEO and CTO are happy to
answer them personally.
Get Beta Access
Anubis Watal
CTO at Anycode
Alex Hudym
CEO at Anycode