CORS and Rate Limiting — Production API Essentials

Why CORS and Rate Limiting Matter

Two concepts that every API developer must understand before deploying to production are CORS (Cross-Origin Resource Sharing) and rate limiting. CORS controls which websites can call your API from a browser. Rate limiting protects your API from abuse, accidental or malicious.

Why this matters for your career:

  • CORS errors are among the most common frustrations for frontend developers
  • Misconfigured CORS can expose your API to unauthorized websites (security risk)
  • Rate limiting is essential for production APIs that serve real users
  • API rate limiting is a key interview topic for backend roles
  • Freelance projects often require rate limiting implementation for client APIs

What Is CORS?

CORS is a browser security mechanism that restricts web pages from making requests to a different domain than the one that served the page. For example, if your frontend is at https://myapp.com and your API is at https://api.myapp.com, the browser blocks the request unless the API explicitly allows it via CORS headers.

How CORS Works

When a browser makes a cross-origin request, it first sends a "preflight" OPTIONS request:

OPTIONS /api/v1/users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

The server responds with:

Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

If the server does not include the appropriate headers, the browser blocks the request and shows a CORS error in the console.

Configuring CORS in Your Backend

Node.js (Express):

const cors = require('cors');

// Allow specific origin
app.use(cors({
  origin: 'https://myapp.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

// Or allow multiple origins
const allowedOrigins = ['https://myapp.com', 'https://admin.myapp.com'];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

Python (FastAPI):

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

CORS Configuration Guide

| Header | Purpose | Example Value | |--------|---------|---------------| | Access-Control-Allow-Origin | Which origins are allowed | https://myapp.com or * | | Access-Control-Allow-Methods | Which HTTP methods are permitted | GET, POST, PUT, DELETE | | Access-Control-Allow-Headers | Which custom headers are allowed | Content-Type, Authorization | | Access-Control-Max-Age | How long to cache the preflight response (seconds) | 86400 (24 hours) | | Access-Control-Allow-Credentials | Whether cookies/auth headers are allowed | true | | Access-Control-Expose-Headers | Which headers are accessible to JavaScript | X-Request-Id, X-RateLimit-Remaining |

Common CORS Mistakes

| Mistake | Problem | |---------|--------| | Using * in production | Allows ANY website to call your API | | Not handling OPTIONS preflight | Browser blocks all cross-origin POST/PUT/DELETE | | Forgetting credentials: true | Cookies and auth headers are not sent | | Not exposing custom headers | JavaScript cannot read custom response headers | | CORS configured on only some endpoints | Inconsistent behavior confuses developers | | Wildcard with credentials | Browsers reject this combination |

What Is Rate Limiting?

Rate limiting controls how many requests a client can make within a given time window. It prevents a single user or IP from overwhelming your server.

Why Rate Limit?

| Reason | Example | |--------|--------| | Prevent abuse | A malicious user sends 1M requests per minute | | Protect resources | Database queries, compute time, bandwidth | | Ensure fair usage | One tenant should not degrade service for others | | Implement tiered pricing | Free tier: 100 req/hr, Premium: 10,000 req/hr | | Defend against DDoS | Limit the impact of distributed attacks |

Rate Limiting Strategies

1. Fixed Window:

100 requests per hour, resets at the top of each hour

Simple but can burst at window boundaries.

2. Sliding Window:

100 requests per rolling 60-minute window

More accurate, prevents boundary bursts.

3. Token Bucket:

10 tokens per second, max 100 burst

Allows short bursts while capping long-term rate.

4. Leaky Bucket:

Process 5 requests per second, queue the rest

Smoothes traffic spikes.

Implementing Rate Limiting (Node.js Example)

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

// Global rate limit
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per window
  message: {
    error: {
      code: 'RATE_LIMIT_EXCEEDED',
      message: 'Too many requests, please try again later.'
    }
  },
  headers: true, // sends RateLimit headers
  standardHeaders: true,
  legacyHeaders: false
});

app.use(globalLimiter);

// Strict limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // maximum 5 login attempts per 15 minutes
  message: {
    error: {
      code: 'AUTH_RATE_LIMIT',
      message: 'Too many login attempts, account temporarily locked.'
    }
  }
});

app.use('/api/v1/auth/login', authLimiter);

Rate Limit Response Headers

When rate limiting is active, your API should return these headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1691234567

When the limit is exceeded:

HTTP/1.1 429 Too Many Requests
Retry-After: 3600

CORS + Rate Limiting Together

Both CORS and rate limiting should be configured before any API goes to production:

| Concern | CORS | Rate Limiting | |---------|------|--------------| | Problem it solves | Unauthorized websites calling your API | Overuse and abuse of your API | | Layer | Browser-level | Server-level | | Configured in | Response headers | Application middleware or reverse proxy | | Typical error | CORS error in browser console | HTTP 429 Too Many Requests | | Bypassed by | Server-to-server calls (no browser) | Distributed attacks from many IPs |

Summary

CORS and rate limiting are essential for any production API. CORS ensures only authorized origins can call your API from browsers. Rate limiting protects your backend from overload and abuse. Together, they form the baseline security and reliability layer for public APIs.

Key takeaways:

  • CORS is enforced by browsers, not servers — server-to-server calls ignore it
  • Always restrict Access-Control-Allow-Origin to specific domains in production
  • Handle OPTIONS preflight requests explicitly
  • Rate limiting prevents abuse and ensures fair resource allocation
  • Use sliding window or token bucket algorithms for accuracy
  • Always return standard rate limit headers (X-RateLimit-*)
  • Return HTTP 429 with Retry-After when limit is exceeded
  • Different endpoints may need different rate limits (auth, public, premium)

What's Next: Building API Key Authentication

The next chapter walks through building a complete API key authentication system — generating keys, validating requests, rate limiting per key, and building a developer dashboard.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!