ScaledByDesign/Insights
ServicesPricingAboutContact
Book a Call
Scaled By Design

Fractional CTO + execution partner for revenue-critical systems.

Company

  • About
  • Services
  • Contact

Resources

  • Insights
  • Pricing
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service

© 2026 ScaledByDesign. All rights reserved.

contact@scaledbydesign.com

On This Page

The Gateway EvolutionPattern 1: Simple Reverse ProxyPattern 2: Authentication GatewayPattern 3: Rate LimitingPattern 4: Backend for Frontend (BFF)Gateway Selection GuideAnti-Patterns to Avoid
  1. Insights
  2. Architecture
  3. API Gateway Patterns That Scale — From Simple Proxy to Platform

API Gateway Patterns That Scale — From Simple Proxy to Platform

June 5, 2026·ScaledByDesign·
api-gatewayarchitecturemicroservicesscalabilityinfrastructure

The Gateway Evolution

Every API gateway follows the same evolution: reverse proxy → authentication layer → rate limiter → request transformer → orchestration layer → unmaintainable monolith. Understanding this trajectory helps you make better decisions at each stage.

A fintech startup started with nginx as a reverse proxy. Eighteen months later, their nginx config was 3,000 lines with embedded Lua scripts handling auth, rate limiting, request transformation, and circuit breaking. Deployments required a networking expert. Nobody else could touch it.

Pattern 1: Simple Reverse Proxy

Start here. Route requests to the right service:

# Good enough for < 5 services
upstream order-service {
  server order-service:3000;
}
 
upstream product-service {
  server product-service:3001;
}
 
server {
  location /api/orders {
    proxy_pass http://order-service;
  }
  location /api/products {
    proxy_pass http://product-service;
  }
}

When to use: You have fewer than 5 services and simple routing needs. Don't over-engineer this.

Pattern 2: Authentication Gateway

The first real responsibility your gateway takes on:

// Express middleware as gateway
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
 
const app = express();
 
// Authentication middleware — runs before all routes
app.use("/api/*", async (req, res, next) => {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token) return res.status(401).json({ error: "Unauthorized" });
 
  try {
    const user = await verifyJWT(token);
    req.headers["x-user-id"] = user.id;
    req.headers["x-user-role"] = user.role;
    next();
  } catch {
    return res.status(401).json({ error: "Invalid token" });
  }
});
 
// Route to services — they trust the gateway's auth headers
app.use("/api/orders", createProxyMiddleware({
  target: "http://order-service:3000",
  pathRewrite: { "^/api/orders": "" },
}));

Key principle: Services behind the gateway trust the gateway's authentication headers. They don't re-verify JWTs. This keeps authentication logic in one place.

Pattern 3: Rate Limiting

Protect your services from abuse and cascading failures:

import { RateLimiterRedis } from "rate-limiter-flexible";
 
const rateLimiters = {
  // Different limits for different endpoints
  default: new RateLimiterRedis({
    storeClient: redis,
    keyPrefix: "rl:default",
    points: 100,        // requests
    duration: 60,        // per 60 seconds
  }),
  auth: new RateLimiterRedis({
    storeClient: redis,
    keyPrefix: "rl:auth",
    points: 5,           // 5 login attempts
    duration: 300,       // per 5 minutes
  }),
  webhook: new RateLimiterRedis({
    storeClient: redis,
    keyPrefix: "rl:webhook",
    points: 1000,        // webhooks need higher limits
    duration: 60,
  }),
};
 
app.use("/api/*", async (req, res, next) => {
  const limiter = selectLimiter(req.path);
  const key = req.headers["x-user-id"] || req.ip;
 
  try {
    const result = await limiter.consume(key);
    // Set rate limit headers
    res.set("X-RateLimit-Remaining", String(result.remainingPoints));
    res.set("X-RateLimit-Reset", String(Math.ceil(result.msBeforeNext / 1000)));
    next();
  } catch {
    res.status(429).json({ error: "Too many requests" });
  }
});

Pattern 4: Backend for Frontend (BFF)

When different clients need different API shapes:

// Mobile BFF — aggregates data, minimizes round trips
app.get("/mobile/api/home", async (req, res) => {
  const userId = req.headers["x-user-id"];
 
  // Parallel fetch from multiple services
  const [user, recommendations, cart, notifications] = await Promise.all([
    fetch(`http://user-service/users/${userId}`).then(r => r.json()),
    fetch(`http://recommendation-service/for/${userId}?limit=10`).then(r => r.json()),
    fetch(`http://cart-service/carts/${userId}`).then(r => r.json()),
    fetch(`http://notification-service/unread/${userId}`).then(r => r.json()),
  ]);
 
  // Single response shaped for the mobile home screen
  res.json({
    greeting: `Welcome back, ${user.firstName}`,
    recommendedProducts: recommendations.map(r => ({
      id: r.id,
      name: r.name,
      image: r.images[0]?.thumbnail,  // Mobile-optimized image
      price: r.price,
    })),
    cartItemCount: cart.items.length,
    unreadNotifications: notifications.count,
  });
});

Gateway Selection Guide

| Need                          | Solution                      |
|-------------------------------|-------------------------------|
| Simple routing (< 5 services) | nginx / Caddy                |
| Auth + rate limiting           | Kong / Express gateway       |
| GraphQL federation             | Apollo Router / Cosmo        |
| Full platform (enterprise)     | AWS API Gateway / Apigee     |
| Kubernetes-native              | Istio / Envoy / Traefik      |

Decision criteria:
  → Team under 20 engineers? Kong or Express-based gateway
  → Already on Kubernetes? Istio or Traefik ingress
  → Need managed? AWS API Gateway + Lambda authorizer
  → GraphQL? Apollo Router for federation

Anti-Patterns to Avoid

1. Business logic in the gateway
   ✗ Order validation, pricing calculations, inventory checks
   ✓ Keep the gateway thin: auth, rate limiting, routing

2. Synchronous orchestration
   ✗ Gateway calls 5 services sequentially to build a response
   ✓ Use BFF pattern for aggregation, async events for orchestration

3. Single point of failure
   ✗ One gateway instance handling all traffic
   ✓ Multiple instances behind a load balancer, health checks

4. Gateway as integration layer
   ✗ Request/response transformation between incompatible services
   ✓ Anti-corruption layer in the consuming service instead

5. Shared gateway for internal and external APIs
   ✗ Same gateway, same rules for partners and internal services
   ✓ Separate gateways: external (strict) and internal (fast)

Your API gateway should be boring infrastructure, not a clever engineering project. It should do authentication, rate limiting, and routing. Everything else belongs in the services themselves. The moment your gateway becomes "smart," it becomes a bottleneck — for both traffic and team velocity.

Previous
Experimentation Platforms — Build vs Buy Decision Framework
Insights
API Gateway Patterns That Scale — From Simple Proxy to PlatformDomain-Driven Design Without the PhD — A Practical GuideMicroservices Communication Patterns — Sync, Async, and When to Use EachMonorepo vs. Polyrepo — The Decision Framework Nobody Gives YouAPI Versioning Strategies That Don't Become a Maintenance NightmareEvent-Driven Architecture Without the PhD — A Practical GuideCQRS Without the Complexity — A Practical Implementation GuideThe Strangler Fig Migration That Saved a 10-Year-Old MonolithWhy You Should Start With a MonolithEvent-Driven Architecture for the Rest of UsThe Real Cost of Microservices at Your ScaleThe Caching Strategy That Cut Our Client's AWS Bill by 60%API Design Mistakes That Will Haunt You for YearsMulti-Tenant Architecture: The Decisions You Can't UndoCI/CD Pipelines That Actually Make You FasterThe Rate Limiting Strategy That Saved Our Client's APIWhen to Rewrite vs Refactor: The Decision Framework

Ready to Ship?

Let's talk about your engineering challenges and how we can help.

Book a Call