Skip to main content
LadeStack LogoLadeStack
API Design & Scaling 12 min read September 15, 2024

Building API Gateways for Microservices: Routing, Resiliency, and Security

Architectural blueprint for engineering high-throughput API gateways: reverse proxy routing, JWT validation, token-bucket throttling, Envoy configurations, and defense-in-depth.

API Gateways for Microservices Architecture Diagram

1. The Architectural Role of the Edge API Gateway

In a microservices topology, exposing internal domain services directly to client applications introduces severe operational liabilities: client-to-service protocol coupling, chatty mobile communication over high-latency networks, fragmentation of cross-cutting security policies, and zero blast-radius containment during downstream outages.

An API Gateway acts as the single reverse proxy ingress point mediating between external consumers (single-page applications, mobile apps, third-party webhook consumers, partner APIs) and internal microservice clusters. It encapsulates the underlying system topology, presenting consolidated, versioned, and secure endpoints while offloading cross-cutting operational concerns.

+-------------------------------------------------------------------+
|                        Client Applications                        |
|             (SPA / Mobile iOS & Android / Partner APIs)           |
+---------------------------------+---------------------------------+
                                  | TLS 1.3 / HTTP/2 / HTTP/3
                                  v
+-------------------------------------------------------------------+
|                         Edge API Gateway                          |
|  - TLS Termination & HTTP/2 Ingress                               |
|  - Path & Header Routing (BFF Pattern)                            |
|  - Centralized Authentication & JWT Signature Verification        |
|  - Distributed Rate Limiting & Quota Management (Redis Cluster)   |
|  - WAF Filtering, CORS Handling, Request Scrubbing                |
|  - Distributed Tracing Propagation (W3C Trace Context)            |
+--------+------------------------+------------------------+--------+
         | gRPC / mTLS            | gRPC / mTLS            | gRPC / mTLS
         v                        v                        v
+-----------------+      +-----------------+      +-----------------+
|  Auth & User    |      | Orders & Cart   |      | Payment Engine  |
|  Microservice   |      |  Microservice   |      |  Microservice   |
+-----------------+      +-----------------+      +-----------------+

Essential Responsibilities vs Service Mesh Boundaries

A recurring architectural failure in cloud-native systems is confusing the role of an API Gateway with that of a Service Mesh (such as Istio, Linkerd, or Cilium Service Mesh). While both technologies utilize reverse proxy engines (predominantly Envoy), their design targets and operational scopes are fundamentally distinct:

Dimension Edge API Gateway (North-South) Service Mesh (East-West)
Traffic Direction External consumers entering internal networks (North-South). Service-to-service internal communication (East-West).
Primary Focus API protocol translation, public rate limiting, external auth, billing, client SDK alignment. Mutual TLS (mTLS), fine-grained pod access policies, canary rollouts, internal telemetry.
Deployment Model Centralized or clustered ingress tier at the edge of the VPC. Sidecar proxies colocated inside application pods or ambient kernel eBPF nodes.
Authentication Scope Coarse-grained consumer identity verification (OAuth 2.0, OpenID Connect, JWTs, API keys). Cryptographic service identity verification (SPIFFE/SPIRE, x509 service certificates).

Using an API gateway for inter-service communication creates bottleneck chokepoints and excessive network hops, while exposing a service mesh ingress directly to public clients without gateway defenses leaves internal endpoints vulnerable to credential stuffing and distributed denial-of-service (DDoS) amplification.


2. Reverse Proxy Mechanics & Intelligent Request Routing

At its core, an API gateway is an L7 (Application Layer) reverse proxy that analyzes HTTP headers, methods, query parameters, and SNI hostnames to dispatch requests to upstream clusters.

Modern architectures typically deploy either the Single Ingress Gateway or the Backend-for-Frontend (BFF) pattern. In a BFF architecture, distinct lightweight gateway instances are deployed specifically for mobile apps, web applications, and public API consumers. This prevents the primary gateway from degrading into a fragile, monolithic configuration file maintained by disjoint engineering squads.

Envoy L7 Routing Architecture

Envoy has emerged as the industry-standard high-performance proxy data plane for modern gateways. Below is a production Envoy configuration establishing an edge listener with path rewriting, timeout policies, and retries dispatching to an upstream microservice cluster:

static_resources:
  listeners:
  - name: edge_https_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8443
    filter_chains:
    - transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
          common_tls_context:
            tls_certificates:
            - certificate_chain: { filename: "/etc/envoy/certs/server.crt" }
              private_key: { filename: "/etc/envoy/certs/server.key" }
            alpn_protocols: ["h2,http/1.1"]
      filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          codec_type: AUTO
          route_config:
            name: edge_api_routes
            virtual_hosts:
            - name: api_service_cluster
              domains: ["api.ladestack.in"]
              routes:
              - match:
                  prefix: "/v1/orders"
                route:
                  cluster: orders_microservice_cluster
                  prefix_rewrite: "/orders"
                  timeout: 3.5s
                  retry_policy:
                    retry_on: "5xx,gateway-error,connect-failure"
                    num_retries: 3
                    per_try_timeout: 1.0s
                    retry_back_off:
                      base_interval: 25ms
                      max_interval: 250ms
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: orders_microservice_cluster
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: orders_microservice_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: orders-internal.production.svc.cluster.local
                port_value: 8080

3. Authentication & The Defense-in-Depth Authorization Fallacy

A critical anti-pattern in distributed systems is delegating all security decisions entirely to the edge gateway.

Architectural Law: Gateway authentication verifies who the caller is (identity validation). It does not absolve downstream services from validating what the caller can do on specific domain resources (authorization and fine-grained data tenancy).

Edge Authentication Flow (Token Translation)

To minimize downstream latency and eliminate repeated remote introspection calls against an identity provider (e.g., Auth0, Keycloak), the API gateway acts as a Token Translation Engine:

  1. The client presents an opaque OAuth 2.0 Bearer token or cryptographically signed asymmetric JSON Web Token (JWT).
  2. The gateway validates the token’s signature against a cached JSON Web Key Set (JWKS), verifying claims (iss, aud, exp, nbf).
  3. If valid, the gateway strips sensitive external credentials and injects standardized downstream headers:
    • X-User-Id: The authenticated subject ID (sub).
    • X-Tenant-Id: The tenant/organization namespace.
    • X-User-Roles: Comma-separated roles (admin,billing_manager).
    • X-Trace-Id: Distributed tracing span.
  4. The gateway signs an internal, short-lived (e.g., 60-second) cryptographic HMAC assertion header (X-Gateway-Signature) so internal services can verify the request originated from the authentic gateway proxy rather than an untrusted lateral hop.
Client                  API Gateway                   Upstream Microservice
  |                          |                                  |
  |-- GET /v1/orders ------->|                                  |
  |   Authorization: Bearer  |                                  |
  |                          |-- 1. Validate JWT Signature ---->|
  |                          |-- 2. Verify `exp`, `aud`, `iss`  |
  |                          |-- 3. Extract User & Tenant Data  |
  |                          |                                  |
  |                          |-- GET /orders ------------------>|
  |                          |   X-User-Id: usr_9942            |
  |                          |   X-Tenant-Id: org_0012          |
  |                          |   X-Gateway-Sig: hmac_sha256     |
  |                          |                                  |-- 4. Check DB Row Tenancy
  |                          |                                  |      WHERE id = :id
  |                          |                                  |        AND tenant = :tenant
  |                          |<-- 200 OK (Orders JSON) ---------|
  |<-- 200 OK ---------------|

Downstream Authorization Enforcement

Downstream services must maintain Row-Level Security (RLS) or Domain Access Control rules. If an attacker bypasses the gateway via an SSRF vulnerability or lateral network compromise, downstream services that blindly execute operations without validating that X-User-Id owns the requested resource will suffer catastrophic tenant data leakage.


4. Traffic Shaping: Rate Limiting & Token-Bucket Algorithms

Unchecked traffic surges from buggy frontend loops, scraping bots, or brute-force credential stuffing can cascade through upstream clusters, exhausting database connection pools and memory reserves.

Token-Bucket vs Leaky-Bucket Mechanics

API gateways employ the Token-Bucket Algorithm for rate limiting because it allows controlled bursts while maintaining a strict long-term arrival frequency:

  • A token bucket has a maximum capacity $b$ and refills with tokens at a steady rate of $r$ tokens per second.
  • When an API request arrives, the proxy attempts to draw 1 token from the bucket.
  • If tokens are available, the request proceeds immediately, and remaining tokens are decremented.
  • If the bucket is empty ($0$ tokens), the request is rejected immediately with an HTTP 429 Too Many Requests status code and standard headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.

Redis-Backed Distributed Token Bucket (Atomic Lua Script)

When scaling an API gateway horizontally across multiple nodes, local memory rate limiters allow clients to multiply their quota by the number of active gateway replicas. To enforce a globally consistent quota, gateways synchronize rate limits using Redis.

Because multiple concurrent gateway instances read and write quota keys simultaneously, naive GET and INCR commands cause severe race conditions. An atomic Lua script executed in Redis guarantees thread-safe, atomic quota evaluation in a single round-trip:

-- KEYS[1]: Rate limit key (e.g., "ratelimit:usr_8921:orders")
-- ARGV[1]: Bucket capacity (max burst tokens)
-- ARGV[2]: Refill rate per millisecond
-- ARGV[3]: Current timestamp (milliseconds)
-- ARGV[4]: Requested tokens (usually 1)

local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

-- Retrieve current bucket state: [available_tokens, last_refill_timestamp]
local data = redis.call("HMGET", key, "tokens", "last_updated")
local tokens = tonumber(data[1])
local last_updated = tonumber(data[2])

if tokens == nil then
    -- First request: Initialize full bucket
    tokens = capacity
    last_updated = now
else
    -- Calculate newly accrued tokens since last request
    local elapsed = math.max(0, now - last_updated)
    local generated_tokens = elapsed * refill_rate
    tokens = math.min(capacity, tokens + generated_tokens)
    last_updated = now
end

if tokens >= requested then
    tokens = tokens - requested
    redis.call("HMSET", key, "tokens", tokens, "last_updated", last_updated)
    -- Expire bucket after time required to refill from empty to full + buffer
    local ttl = math.ceil((capacity / refill_rate) / 1000) + 60
    redis.call("EXPIRE", key, ttl)
    return {1, math.floor(tokens)} -- 1 = Allowed, remaining tokens
else
    redis.call("HMSET", key, "tokens", tokens, "last_updated", last_updated)
    return {0, math.floor(tokens)} -- 0 = Denied (429 Too Many Requests)
end

5. Resiliency Patterns: Timeouts, Retries, and Circuit Breakers

A slow microservice is substantially more destructive to a distributed system than a crashed microservice. If downstream Service B experiences database disk queue saturation and responds in 12 seconds instead of 100 milliseconds, incoming requests back up at the edge gateway.

Proxy worker threads and sockets are held open, socket memory exhausts, and the edge gateway begins dropping requests for completely unrelated, healthy services—a classic cascading system failure.

[Healthy Service A] ──┐
                      ├──► [Edge API Gateway] ──► [Unresponsive Service C]
[Healthy Service B] ──┘         ▲                       │ (12s latency)
                                │ Thread/Socket Pool    ▼
                                └────── Exhaustion ◄────┘

Production Resiliency Rules

  1. Mandatory Explicit Timeouts: Never allow the proxy default (often 60s or infinite) to persist. Set strict, per-endpoint timeouts: 500ms for read endpoints; 2.5s for complex orchestrations.
  2. Exponential Backoff and Jitter on Retries: Never retry immediately upon failure. If an upstream service is struggling under heavy load, synchronized retries from hundreds of clients act as a distributed denial-of-service attack (thundering herd). Always inject random jitter: $$T_{\text{backoff}} = \text{base} \times 2^{\text{retry}} + \text{rand}(0, \text{jitter})$$
  3. Idempotency Safeguard: Never retry non-idempotent HTTP methods (POST, PATCH) unless an explicit Idempotency-Key contract is supported and verified. Retrying a raw payment POST after a socket timeout risks charging a customer twice.
  4. Circuit Breaking (Fail Fast): When consecutive failures or timeouts to an upstream cluster cross a threshold (e.g., 50% failures over 10 seconds), open the circuit immediately. Route subsequent requests to an automated fallback response or return an immediate 503 Service Unavailable, preventing proxy thread exhaustion and giving the upstream cluster room to recover.

6. Gateway Implementation Comparison: NGINX, Envoy, and Kong

Selecting the foundational engine for an API gateway requires evaluating throughput performance, configuration dynamism, and operational overhead:

Feature / Metric NGINX Open Source Envoy Proxy Kong Gateway
Primary Architecture Event-driven C worker processes. Multi-threaded C++ asynchronous event loop. OpenResty (NGINX core + LuaJIT engine).
Configuration Updates Reloads configuration files (nginx -s reload), risking connection blips at high scale. 100% Dynamic Discovery Services (xDS API) via gRPC; zero restarts or dropped packets. Dynamic REST Admin API & PostgreSQL/declarative YAML via decK.
Protocol Support HTTP/1.1, HTTP/2, basic gRPC, TCP/UDP stream. Native HTTP/1.1, HTTP/2, HTTP/3, gRPC, Redis, Kafka protocol filters. Native HTTP/1.1, HTTP/2, gRPC, WebSockets, TCP/UDP.
Observability Ecosystem Access logs, metrics via third-party modules or Prometheus exporter. Deep native telemetry: Prometheus metrics, OpenTelemetry, W3C Distributed Tracing, Zipkin/Jaeger. Native OpenTelemetry plugin, Prometheus metrics, Datadog integration.
Best Architectural Fit Static ingress routing, content caching, straightforward reverse proxy setups. Cloud-native orchestration, dynamic Kubernetes ingress, high-performance edge proxying. Enterprise plugin ecosystems (OAuth, SAML, developer portals, billing metering).

7. Distributed Tracing & Edge Observability

When an external request fails across a call graph involving seven downstream services, debugging is impossible without a unified correlation identifier.

The API gateway is responsible for originating or sanitizing W3C Distributed Trace Context headers (traceparent, tracestate). If a malicious client passes an arbitrary traceparent, the gateway must scrub or validate it to prevent trace-injection attacks.

Incoming Request: GET /v1/products/4821
  │
  ├─► [API Gateway]
  │     Creates Span: span_gw_01 (trace_id: 4bf92f3577b34da6a3ce929d0e0e4736)
  │     Injects Header: traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
  │
  ├─► [Product Microservice]
  │     Extracts Trace ID, spawns child span: span_prod_02
  │     Calls Inventory Service with parent: span_prod_02
  │
  └─► [Inventory Microservice]
        Spawns child span: span_inv_03
        Logs DB query latency attached to trace_id: 4bf92f3577b34da6a3ce929d0e0e4736

Essential Edge Gateway Metrics to Monitor

In corporate production environments, gateway monitoring should focus on the Four Golden Signals:

  1. Latency: Percentile distributions ($p50$, $p95$, $p99$) broken down by upstream cluster and route path. A stable $p50$ with an escalating $p99$ indicates upstream database thread contention or garbage collection pauses.
  2. Traffic: Total requests per second (RPS) categorized by client identity, protocol (h2 vs http/1.1), and HTTP method.
  3. Errors: Upstream error rates ($5xx$ HTTP codes) separated from gateway-generated errors (504 Gateway Timeout, 502 Bad Gateway, 429 Too Many Requests).
  4. Saturation: Proxy CPU core utilization, memory consumption, open client connections, and upstream socket pool occupancy.

8. Architectural Summary & Production Checklist

Building an enterprise-ready API gateway demands balancing strict perimeter security with sub-millisecond proxying efficiency.

Domain Architectural Verification Step
Routing Path and header rules are versioned in Git; route timeouts and backoff intervals are explicitly configured.
Security TLS 1.3 enforced at edge; JWT signatures verified against cached JWKS; downstream services validate resource ownership.
Resilience Redis-backed token bucket limits burst traffic; circuit breakers decouple slow upstream services; no raw POST retries.
Observability W3C traceparent headers injected at ingress; access logs structured in JSON; Prometheus golden signals alerted.
Evolution Gateway instances decoupled by client type (BFF pattern); edge configurations isolated from core business logic.

More Engineering Articles