A production-grade REST API handling 10,000+ requests per second with sub-100ms p99 latency. Built with Node.js, Express, and MongoDB. Features JWT authentication, product catalog with variants, idempotent checkout, and real-time inventory using MongoDB aggregation pipelines.
Stateless API layer behind a load balancer, with MongoDB for primary data and Redis for caching/sessions. Horizontal scaling via container orchestration. Database reads routed to replica set secondaries for read-heavy endpoints (product listings).
High-level components
API Gateway / Load Balancer: AWS ALB distributing across ECS Fargate tasks
Refresh Token: Opaque token stored in Redis with 7-day TTL, rotated on each use
Password Hashing: Argon2id (memory-hard, resistant to GPU attacks)
Rate Limiting: 10 req/min on auth endpoints, 100 req/min general (Redis sliding window)
Security Headers: Helmet.js for CSP, HSTS, X-Frame-Options, Referrer-Policy
Why not sessions?
JWT enables stateless horizontal scaling — no sticky sessions, no shared session store latency. Refresh token rotation mitigates token theft risk.
Product Catalog & Variants
Flexible document model in MongoDB supporting unlimited product variants (size, color, material) without schema migrations. Embedded variants for read performance; separate collection for category/facet indexing.
List with filters: Compound index on {categories: 1, "variants.attrs.size": 1, "variants.attrs.color": 1, basePrice: 1}
Search: Text index on searchableText with weights
Single product: Direct _id or slug lookup (cached in Redis 1hr)
Checkout & Idempotent Order Processing
Idempotency keys on every mutating endpoint prevent duplicate orders from network retries. Client generates UUID v4 per checkout attempt; server stores key + response for 24 hours.
Order flow
Client POST /orders with Idempotency-Key header
Server checks Redis for existing key → returns cached response if found
Validate cart, reserve inventory (atomic findOneAndUpdate with $inc on variant stock)
Create order document with PENDING status
Publish OrderCreated event to SQS (payment, email, inventory webhook)
Return order ID + idempotency key to client
Why idempotency matters
Mobile networks, browser retries, and payment provider timeouts cause duplicate requests. Without idempotency: double charges, duplicate fulfillment, inventory corruption. With it: exactly-once semantics at application level.
Real-time Inventory with Aggregation Pipelines
MongoDB aggregation pipelines compute available stock in real-time across variants, reservations, and pending orders. No separate inventory service needed; single source of truth in MongoDB.
Decision: Node.js/Express for primary API. Context: Team expertise, npm ecosystem for auth/validation, fast iteration. Trade-off: Higher CPU/memory per request vs Go; mitigated by horizontal scaling and low absolute cost at this scale.
ADR-002: MongoDB over PostgreSQL for product catalog
Decision: MongoDB for flexible variant schema. Context: Frequent schema changes (new variant attributes), hierarchical categories, no complex joins needed. Trade-off: No ACID transactions across collections; order processing uses application-level compensation.
ADR-003: Eventual consistency for reads
Decision: Read from MongoDB secondaries. Context: Product listings tolerate 100-200ms staleness. Trade-off: Users might see briefly stale stock counts; mitigated by real-time validation at checkout.
ADR-004: Redis for rate limiting vs in-memory
Decision: Redis sliding window. Context: Multi-instance deployment requires shared state. Trade-off: Added latency (~1ms) vs correctness under load.
Lessons Learned
Start with idempotency: Retrofitting idempotency keys is painful. Design it in from day one for any mutating endpoint.
Monitor p99, not average: Average latency hides tail latency spikes that degrade UX. Alert on p99 > 200ms.
Cache invalidation is hard: Use short TTLs (30-60s) for critical data; accept eventual consistency for non-critical.
MongoDB aggregations are powerful: Replaced a separate inventory service with a single pipeline. Less code, fewer failure points.
Load test early: Found connection pool exhaustion at 3K RPS before production. Fixed by tuning pool sizes and ALB idle timeout.
Explore More
View other backend case studies or get in touch to discuss your project.