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-Originto 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-Afterwhen 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.