Case Study · E-Commerce Backend

E-Commerce API: 10K RPS, Sub-100ms p99

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.

Tech: Node.js · Express · MongoDB · Redis · Docker · AWS · ~3 weeks

System Architecture

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

Request flow (simplified)

  1. Client → ALB → Express app
  2. Auth middleware validates JWT (Redis check for revocation)
  3. Route handler executes business logic
  4. Reads: MongoDB secondary (eventually consistent) | Writes: MongoDB primary
  5. Response → Client (with caching headers where appropriate)

Authentication & Authorization

Stateless JWT with short-lived access tokens (15 min) and refresh tokens (7 days) stored in HttpOnly cookies. Token revocation via Redis blacklist for immediate logout/security events.

Implementation details

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.

Data model highlights

// Product document (simplified)
{
  _id: ObjectId,
  slug: "organic-cotton-tee",
  name: "Organic Cotton T-Shirt",
  basePrice: 29.99,
  currency: "USD",
  variants: [
    { sku: "OCT-S-BLK", attrs: { size: "S", color: "Black" }, stock: 42, priceDelta: 0 },
    { sku: "OCT-M-BLU", attrs: { size: "M", color: "Blue" }, stock: 18, priceDelta: 2.00 }
  ],
  categories: ["apparel", "organic", "t-shirts"],
  searchableText: "organic cotton t-shirt black blue small medium...", // for text index
  createdAt, updatedAt
}

Query patterns optimized

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

  1. Client POST /orders with Idempotency-Key header
  2. Server checks Redis for existing key → returns cached response if found
  3. Validate cart, reserve inventory (atomic findOneAndUpdate with $inc on variant stock)
  4. Create order document with PENDING status
  5. Publish OrderCreated event to SQS (payment, email, inventory webhook)
  6. 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.

Available stock calculation (simplified)

db.products.aggregate([
  { $match: { _id: productId } },
  { $unwind: "$variants" },
  { $lookup: {
      from: "orders",
      localField: "variants.sku",
      foreignField: "items.sku",
      pipeline: [
        { $match: { status: { $in: ["PENDING", "CONFIRMED", "PROCESSING"] } } },
        { $group: { _id: "$items.sku", reserved: { $sum: "$items.qty" } } }
      ],
      as: "reservations"
  }},
  { $addFields: {
      available: { $subtract: ["$variants.stock", { $ifNull: [{ $sum: "$reservations.reserved" }, 0] }] }
  }},
  { $project: { sku: "$variants.sku", available: 1, attrs: "$variants.attrs" } }
])

Cached in Redis for 30 seconds on product detail pages. Cache invalidated on order creation via pub/sub.

Performance Metrics & Optimization

Load test results (k6, 5-min sustained): 10,200 RPS, p50=28ms, p95=67ms, p99=94ms, error rate <0.01%. Cost: ~$180/month on AWS (3x Fargate, MongoDB Atlas M30, ElastiCache cache.t3.micro).

Metric Target Achieved Notes
Throughput>10K RPS10.2K RPSSustained 5 min
p50 Latency<50ms28msRead-heavy endpoints
p99 Latency<100ms94msIncludes writes
Error Rate<0.1%0.008%Mostly validation errors
CPU Utilization<70%58%Headroom for spikes
Monthly Cost<$200$180AWS us-east-1

Key optimizations

Key Architecture Decisions (ADRs)

ADR-001: Node.js over Go/Java for API layer

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

Explore More

View other backend case studies or get in touch to discuss your project.