cd /news/developer-tools/how-i-protected-my-express-api-from-… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-89516] src=dev.to β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

How I Protected My Express API from Spam and High AI Costs Using Redis

A developer implemented Redis-based rate limiting to protect an Express API from spam and high AI costs. The solution uses a centralized Redis store to track request counts across multiple server instances, with global and strict limits for normal and AI-heavy routes. The developer highlighted that in-memory rate limiting causes memory leaks and breaks when scaling, while Redis provides a fast, shared counter.

read3 min views1 publishedAug 9, 2026

When I was building my backend API, I realized a big problem: anyone could spam my endpoints.

If a user repeatedly reloads a page or hits an endpoint calling an external AI service, it can crash the server or run up high API costs.

To fix this, I added Rate Limiting. Here is why I used Redis for it and how I set it up.

At first, I thought about saving request counts in a simple JavaScript object:

// ❌ Simple in-memory check (Not good for production)
const requestCounts = {};

app.use((req, res, next) => {
  const ip = req.ip;
  requestCounts[ip] = (requestCounts[ip] || 0) + 1;

  if (requestCounts[ip] > 100) {
    return res.status(429).json({ error: "Too many requests" });
  }
  next();
});

This works locally, but has two big flaws:

1)Memory Leaks: The requestCounts object keeps growing in memory forever.

2)Breaks when Scaling: If you deploy multiple instances of your app behind a load balancer, each server keeps its own count. A user can easily bypass the limit by hitting different servers.

The Solution: Centralized Redis Store

Redis stores data in RAM outside our Node.js app. Because it is centralized, all server instances share the exact same count.

           [ Incoming Client Requests ]
                     β”‚
                     β–Ό
           [ Cloud Load Balancer ]
                     β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β–Ό               β–Ό               β–Ό
[ Express Node 1 ] [ Express Node 2 ] [ Express Node 3 ]
     β”‚               β”‚               β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β–Ό
          [ Central Redis Store ]
         (Checks Request Limits)

How I Configured It in My Project

In my app, I use two levels of protection:

Global Limit: 100 requests per 15 minutes for normal routes.

Strict Limit: 5 requests per 10 minutes for heavy routes (like AI generation or OTP emails).

1. Redis Connection (config/redis.js)

import { createClient } from 'redis';

const redisClient = createClient({
  url: process.env.REDIS_URL || 'redis://localhost:6379'
});

redisClient.on('error', (err) => console.error('Redis Error:', err));
redisClient.on('connect', () => console.log('Connected to Redis'));

await redisClient.connect();

export default redisClient;
js
2. Middleware Setup (middlewares/rateLimiter.js)

import { rateLimit } from 'express-rate-limit';
import { RedisStore } = require('rate-limit-redis'); // or import { RedisStore } from 'rate-limit-redis';
import redisClient from '../config/redis.js';

// Global Limiter (100 req / 15 mins)
export const globalRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  store: new RedisStore({
    sendCommand: (...args) => redisClient.sendCommand(args),
  }),
  message: {
    success: false,
    error: 'Too many requests. Please try again in 15 minutes.'
  }
});

// Strict Limiter for Heavy Routes (5 req / 10 mins)
export const aiRateLimiter = rateLimit({
  windowMs: 10 * 60 * 1000,
  limit: 5,
  store: new RedisStore({
    sendCommand: (...args) => redisClient.sendCommand(args),
  }),
  message: {
    success: false,
    error: 'Limit reached for AI requests. Please wait 10 minutes.'
  }
});
python
3. Applying to Routes (routes/aiRoutes.js)

import express from 'express';
import { globalRateLimiter, aiRateLimiter } from '../middlewares/rateLimiter.js';

const router = express.Router();

// Apply global limit to all endpoints in this router
router.use(globalRateLimiter);

// Protect heavy AI route with strict limit
router.post('/upload-resume-and-analyze', aiRateLimiter, (req, res) => {
  res.status(202).json({
    success: true,
    message: 'Request accepted for background processing.'
  });
});

export default router;

What I Learned

1)Stop requests early: Blocking bad traffic at the middleware layer saves database reads and server CPU cycles.

2)Redis is fast: Checking limits in Redis takes less than 1ms.

3)Remember trust proxy: If hosting on Render or behind Nginx, add app.set('trust proxy', 1) in Express so it reads the user's real IP instead of the load balancer IP.

πŸ’» GitHub:https://github.com/nikhilsingh2764/invoice-Genrator

πŸš€ Live API:https://invoice-backend-drqr.onrender.com/

πŸ“‘ Postman Collection: (https://www.postman.com/technical-physicist-35686083-s-team/invoice-generator-api/collection/39798617-83cff721-5ce7-4e00-ba58-0d49017d3f39?action=share&creator=39798617)

── more in #developer-tools 4 stories Β· sorted by recency
── more on @redis 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/how-i-protected-my-e…] indexed:0 read:3min 2026-08-09 Β· β€”