---
title: "Protect Your API: Why You Need Rate Limiting (and How to Add It)"
date: "2026-01-14"
slug: "protect-your-api-with-rate-limiting"
author: "Dany Paredes"
canonical: "https://danywalls.com/protect-your-api-with-rate-limiting"
description: "I was explaining to a friend why validating emails isn't enough. Learn how to stop bots from killing your subscription endpoint using Rate Limiting—from a naive in-memory solution to a robust Redis implementation."
---


Yesterday, I was talking to a friend about the importance of security when building applications. We were discussing a classic feature: **a newsletter subscription form**.

You know the drill. You create an endpoint, say `/api/subscribe`, and you add the usual validations:
1.  Is the email field empty?
2.  Does it look like a real email (regex)?
3.  Is this person already subscribed?

"That's enough, right?" he asked. 

"Well," I said, "it's enough to keep your database clean, but it's not enough to keep your server alive."

The problem isn't just bad data—it's **volume**. Without protection, a simple bot script could hit your endpoint 10,000 times in a minute. Even if you reject the bad emails, your database is still working hard to check if they exist. Your server is still parsing JSON. You are paying for those cycles.

**What problem does this solve for us as developers?** Rate limiting ensures that no single user (or bot) can degrade the experience for everyone else or bankrupt us with server costs.

## The Concept

Think of Rate Limiting as a **bouncer at a club**. The bouncer doesn't care if your ID is valid (that's the data validation); he cares about how many people are trying to push through the door at once. He keeps the flow manageable.

## The "Do It Yourself" Approach (And Why It Fails)

Let's say we want to limit users to **5 requests every 60 seconds**.

Your first instinct might be: *"I don't need a fancy library. I'll just use a Javascript Map in memory."*

It might look something like this in a Next.js API route:

```typescript
const rateLimitMap = new Map();

export async function POST(request: Request) {
  const ip = request.headers.get("x-forwarded-for") || "127.0.0.1";
  const now = Date.now();
  const windowSize = 60 * 1000; // 60 seconds

  // Get user's recent request timestamps
  const userHistory = rateLimitMap.get(ip) || [];
  
  // Filter out timestamps older than 60 seconds
  const recentRequests = userHistory.filter(timestamp => now - timestamp < windowSize);

  if (recentRequests.length >= 5) {
    return Response.json(
      { error: "Too many requests, slow down!" }, 
      { status: 429 }
    );
  }

  // Record this request
  recentRequests.push(now);
  rateLimitMap.set(ip, recentRequests);

  // ... proceed with subscription logic ...
  return Response.json({ message: "Subscribed!" });
}
```

### The Problem with "In-Memory"

This code works perfectly... **on your local machine**. But the moment you deploy this to a serverless environment like Vercel or AWS Lambda, it breaks.

1.  **Statelessness**: Serverless functions spin up and die. Next time the function runs, your `rateLimitMap` is empty.
2.  **Multiple Islands**: If your app scales to handle traffic, you might have 50 different instances running at once. Instance A doesn't know that the user just hit Instance B 500 times.

So, how do we share this "memory" across thousands of short-lived server instances?

## The Solution: Redis (Upstash)

To solve this, we need an external memory—a database that is fast enough to check "has this IP made 5 requests?" in milliseconds. **Redis** is the standard tool for this.

For serverless apps, I love using **Upstash**. It's Redis over HTTP, so you don't need to manage persistent connections.

### Setup

Let's clean up our naive code. First, install the specialized library:

```bash
npm install @upstash/ratelimit @upstash/redis
```

Grab your `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the [Upstash Console](https://console.upstash.com/) and add them to your `.env` file.

### Implementation

Now, protecting our `/api/subscribe` endpoint is incredibly simple. We don't need to write logic for sliding windows or timestamps; the library handles it.

```typescript
// app/api/subscribe/route.ts
import { NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { headers } from "next/headers";

// Create a unified limiter instance
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, "60 s"), // 5 requests per 60s
  analytics: true,
});

export async function POST(request: Request) {
  // Use IP as key (or user ID if logged in)
  const ip = headers().get("x-forwarded-for") || "127.0.0.1";
  
  // Ask Redis: "Can this guy pass?"
  const { success } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      { error: "Too many requests. Please try again later." },
      { status: 429 }
    );
  }

  // ✅ Safe to process the subscription
  const body = await request.json();
  
  // Example subscription logic
  console.log(`Subscribing email: ${body.email}`);

  return NextResponse.json({ message: "Successfully subscribed!" });
}
```

So far we've set up the limiter and applied it to a route. This moves the state to Upstash, so it doesn't matter if your API is running on one server or a thousand Lambda functions across the globe.

## Summary

Rate Limiting is not just a "nice to have" feature; it's a fundamental security practice.

*   **We learned** that functional validation (regex) isn't enough to protect resources.
*   **We saw** why in-memory solutions fail in modern serverless architectures.
*   **We built** a robust, distributed rate limiter with Redis.

### Challenge

Now that you have this working, try to extend it:
1.  Can you create different limits for free vs. premium users? ($user.plan === 'pro')
2.  Try blocking specific IPs permanently if they exceed a "ban" threshold.

Happy coding!

_Photo by [Rohit Tandon](https://unsplash.com/@sepoys?utm_source=unsplashx&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=unsplashx&utm_medium=referral)_

