BACK TO INSIGHTS
TECH: System Design & APIsFEB 18, 20267 MIN READBY Anuj Bansal

Designing Scalable APIs for High Traffic Systems

Patterns and strategies we follow to design APIs that stay fast, reliable and predictable even under massive load.

SHARE THIS ARTICLE:
System Design & APIs
10k req/sLOADBALANCERNode #1Node #2Node #3REDISToken Bucket
💡Key Takeaways & Executive Summary
  • Implement Redis token-bucket rate limiters to protect downstream services from volumetric spikes.
  • Replace offset-based pagination (skip/limit) with cursor-based pagination to maintain sub-10ms query speeds on million-row tables.
  • Offload compute-heavy tasks (PDF generation, email sending, data exports) to asynchronous background job queues (BullMQ).
  • Leverage HTTP conditional caching headers (ETag, Cache-Control) to serve repeat requests directly from edge caches.

When an API experiences sudden traffic spikes, standard CRUD architectures often degrade: database connection pools saturate, memory spikes trigger process restarts, and response latencies rise.

Maintaining predictable sub-50ms latencies under heavy concurrent load requires three foundational backend patterns.

1. Atomic Rate Limiting with Redis

Unbounded endpoints leave backend services vulnerable to runaway client loops and scraping. Implementing atomic rate limiting with Redis protects service availability:

typescript
import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

export async function rateLimiter(req: Request, res: Response, next: NextFunction) { const ip = req.ip || 'unknown'; const key = rate_limit:${ip}; const currentRequests = await redis.incr(key); if (currentRequests === 1) { await redis.expire(key, 60); }

if (currentRequests > 100) { return res.status(429).json({ error: 'Too Many Requests', message: 'Rate limit exceeded. Please retry in 60 seconds.' }); }

next(); } ```

2. Cursor-Based Pagination Over Offset Queries

Offset pagination using skip(10000).limit(20) forces database engines to scan and discard 10,000 index entries to return 20 records. Indexing the sorting key and passing the last seen identifier as a cursor turns pagination into a direct O(1) B-tree seek:

typescript
const records = await OrderModel.find({
  _id: { $gt: cursorId }
})
.limit(20)
.sort({ _id: 1 });

3. Asynchronous Job Processing with BullMQ

Operations involving file generation, PDF rendering, or third-party webhooks should never block request threads. Acknowledge requests immediately with an HTTP 202 Accepted response and process execution in an isolated background queue:

typescript
app.post('/api/reports/generate', async (req, res) => {
  const job = await reportQueue.add('generate-pdf', {
    userId: req.user.id,
    reportRange: req.body.range
  });

return res.status(202).json({ message: 'Report generation queued', jobId: job.id }); }); ```

Combining distributed rate limiting, cursor pagination, and asynchronous job queues ensures backend APIs maintain steady response times through sudden traffic surges.

Read the complete guide: Building a Production-Ready Web Application in 2026: From Idea to Scalable Product
AB
ABOUT THE AUTHOR

Anuj Bansal

Anuj Bansal is a freelance full stack developer based in Indore, India specializing in scalable Next.js architectures, React web applications, Node.js backends, and high-performance server infrastructure. Looking to build a production-ready product? Hire Anuj for your next web application or SaaS platform.

🚀READY FOR PRODUCTION

Planning to Build a Production-Ready Web Application?

From system architecture and Next.js engineering to database optimization and technical SEO — let’s build a fast, scalable web product tailored to your business goals.

Available for New Projects Free 30-Min Architecture Discovery💬 Direct Engineering Access
Current: 1. Atomic Rate Limiting with Redis

Get engineering notes in your inbox.

Real-world lessons, system design deep-dives, and production stories — delivered weekly.