Rate Limiter in Node.js: Complete Beginner-Friendly Guide

Modern web applications receive thousands of requests every day. Without proper protection, your API can become vulnerable to abuse, brute-force attacks, excessive traffic, and even server crashes.

This is where a Rate Limiter in Node.js becomes extremely important.

A rate limiter controls how many requests a user can make within a specific period. It helps protect your application, improve performance, and ensure fair usage of your APIs.

In this guide, you will learn:

  • What a rate limiter is
  • Why it is important
  • How to implement a rate limiter in Node.js
  • How to use it with Express
  • How to use it with Apollo Server GraphQL APIs
  • Best practices
  • Common mistakes to avoid

By the end of this article, you’ll be able to implement a production-ready Rate Limiter in Node.js.

rate limiter

What Is a Rate Limiter?

A rate limiter is a mechanism that restricts the number of requests a client can make within a specific time window.

For example:

Time WindowMaximum Requests
1 minute100
1 hour1000
1 day10000

If the limit is exceeded, the server returns:

429 Too Many Requests

Why Use a Rate Limiter in Node.js?

Without rate limiting, your API is vulnerable to:

DDoS Attacks

Attackers can send millions of requests.

Brute Force Attacks

Login endpoints are common targets.

Resource Exhaustion

Expensive database queries can overload your server.

Third-Party API Limits

If your application calls external services, excessive requests may exceed vendor limits.

Fair Usage

Prevents a single user from consuming all resources.


How Rate Limiting Works

A rate limiter tracks requests from a user based on:

  • IP Address
  • User ID
  • API Key
  • Session ID

Example:

User IP: 192.168.1.100

Allowed:
100 requests / minute

Request count:

1
2
3
...
100

Request 101:

429 Too Many Requests

Types of Rate Limiting Algorithms

Fixed Window

Simple implementation.

Example:

100 requests per minute

Counter resets every minute.

Pros

  • Easy

Cons

  • Traffic spikes at boundary times

Sliding Window

Tracks requests over a rolling period.

Example:

Last 60 seconds

More accurate than Fixed Window.


Token Bucket

Each user receives tokens.

Example:

100 tokens

Each request consumes a token.

Tokens refill over time.

Best For

  • APIs
  • Microservices

Leaky Bucket

Requests enter a queue and are processed at a fixed rate.

Best For

  • Traffic smoothing

Implementing Rate Limiter in Node.js

Let’s build one using Express.

Install packages:

npm install express express-rate-limit

Express Rate Limiter Example

Create:

app.jsCode language: CSS (css)
import express from "express";
import rateLimit from "express-rate-limit";

const app = express();

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: {
    error: "Too many requests. Try again later."
  }
});

app.use(limiter);

app.get("/", (req, res) => {
  res.send("API Running");
});

app.listen(3000);Code language: JavaScript (javascript)

This configuration allows:

100 requests every 15 minutes

Applying Rate Limiting Only to Login Routes

Login endpoints need stricter limits.

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: "Too many login attempts"
});

app.use("/login", loginLimiter);Code language: PHP (php)

Now users can only attempt login:

5 times every 15 minutes

Custom Rate Limiter in Node.js

Sometimes you need more control.

Example:

const requestMap = new Map();

function customRateLimiter(req, res, next) {
  const ip = req.ip;

  const currentTime = Date.now();

  if (!requestMap.has(ip)) {
    requestMap.set(ip, {
      count: 1,
      startTime: currentTime
    });

    return next();
  }

  const user = requestMap.get(ip);

  if (currentTime - user.startTime > 60000) {
    user.count = 1;
    user.startTime = currentTime;
    return next();
  }

  user.count++;

  if (user.count > 100) {
    return res.status(429).json({
      error: "Rate limit exceeded"
    });
  }

  next();
}Code language: JavaScript (javascript)

Apply middleware:

app.use(customRateLimiter);Code language: PHP (php)

Rate Limiting in Apollo Server GraphQL

GraphQL APIs can also be abused.

Example Apollo Server:

import { ApolloServer } from "@apollo/server";Code language: JavaScript (javascript)

Add middleware before GraphQL:

app.use(
  "/graphql",
  rateLimit({
    windowMs: 60000,
    max: 50
  })
);Code language: PHP (php)

Complete example:

app.use(
  "/graphql",
  rateLimit({
    windowMs: 60000,
    max: 50,
    message: {
      error: "GraphQL rate limit exceeded"
    }
  })
);Code language: JavaScript (javascript)

This protects expensive GraphQL queries.


Using Redis for Distributed Rate Limiting

In production, multiple Node.js servers may be running.

Using memory-based storage won’t work.

Use Redis instead.

Install:

npm install redis
npm install rate-limit-redis

Example:

import RedisStore from "rate-limit-redis";
import { createClient } from "redis";

const redisClient = createClient();

const limiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) =>
      redisClient.sendCommand(args)
  }),
  windowMs: 15 * 60 * 1000,
  max: 100
});Code language: JavaScript (javascript)

Benefits:

  • Shared counters
  • Works across multiple servers
  • Production ready

Real-World Example: External API Rate Limit

Suppose your application calls an external API that allows:

40 requests per second

You can build a queue.

Example:

class SimpleRateLimiter {
  constructor(maxRequests, intervalMs) {
    this.maxRequests = maxRequests;
    this.intervalMs = intervalMs;
    this.queue = [];
    this.running = false;
  }

  async schedule(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        task,
        resolve,
        reject
      });

      this.run();
    });
  }

  async run() {
    if (this.running) return;

    this.running = true;

    while (this.queue.length) {
      const batch = this.queue.splice(
        0,
        this.maxRequests
      );

      await Promise.all(
        batch.map(async item => {
          try {
            const result = await item.task();
            item.resolve(result);
          } catch (error) {
            item.reject(error);
          }
        })
      );

      await new Promise(resolve =>
        setTimeout(resolve, this.intervalMs)
      );
    }

    this.running = false;
  }
}Code language: JavaScript (javascript)

Usage:

const limiter = new SimpleRateLimiter(
  40,
  1000
);

await limiter.schedule(() =>
  axios.get(url)
);Code language: JavaScript (javascript)

This ensures:

Maximum 40 requests per second

Best Practices for Rate Limiter in Node.js

Use Different Limits

Different routes need different limits.

Example:

Login: 5/minute
Search: 100/minute
Public API: 1000/minuteCode language: PHP (php)

Use Redis in Production

Memory-based storage resets on restart.

Redis provides persistence.


Return Helpful Messages

Bad:

{
  "error": "Denied"
}Code language: JSON / JSON with Comments (json)

Good:

{
  "error": "Rate limit exceeded",
  "retryAfter": 60
}Code language: JSON / JSON with Comments (json)

Monitor Rate Limit Events

Log excessive requests.

Example:

logger.warn(
  "Rate limit exceeded",
  {
    ip: req.ip
  }
);Code language: CSS (css)

Protect GraphQL Endpoints

GraphQL queries can be expensive.

Always add rate limiting.


Common Mistakes

Using Memory Store in Production

Bad:

const limiter = rateLimit();Code language: JavaScript (javascript)

Problem:

Counters disappear after restart

One Limit for Every Route

Different endpoints require different limits.


Ignoring Internal APIs

Internal APIs can also be abused.

Apply limits everywhere.


Not Handling Proxy Servers

Behind:

  • Nginx
  • Azure Application Gateway
  • AWS Load Balancer

Enable:

app.set("trust proxy", 1);Code language: JavaScript (javascript)

Otherwise IP tracking may fail.


Not Returning HTTP 429

Always use:

429 Too Many Requests

for rate-limit violations.


Performance Benefits of Rate Limiting

A properly implemented Rate Limiter in Node.js helps:

  • Reduce server load
  • Prevent abuse
  • Improve response times
  • Protect databases
  • Protect third-party APIs
  • Improve application stability

For high-traffic applications, rate limiting is not optional—it is essential.

Conclusion

Implementing a Rate Limiter in Node.js is one of the most important security and performance improvements you can make for your application. Whether you are building REST APIs with Express or GraphQL APIs with Apollo Server, rate limiting helps prevent abuse, protects backend resources, and improves reliability.

For small projects, express-rate-limit is an excellent choice. For production environments with multiple servers, combine rate limiting with Redis. If you need to respect third-party API quotas, a custom queue-based rate limiter can provide precise control over request throughput.

By following the techniques and best practices covered in this guide, you can build secure, scalable, and production-ready Node.js applications.

Leave a Comment

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