backend / microservices / 06_api_gateway.md

API Gateway

5 interview angles 5 min read source

API Gateway

The API gateway is the single entry point between clients and your microservice backend. It owns the things that don’t belong duplicated in every service: TLS termination, auth, rate limiting, routing, request shaping.

What it does

Concern Without gateway With gateway
TLS termination every service gateway only
Authentication (token validation) every service gateway; pass user-id header inward
Rate limiting per-service ad hoc central
Request routing (path → service) DNS or client knowledge declarative rules
Versioning per service uniform /v1/..., /v2/... at the edge
CORS every service one place
Response aggregation (BFF) clients call N services gateway fans out, merges
Caching per service edge cache at the gateway
WAF / DDoS every service gateway / CDN

Common gateways

Gateway Notes
AWS API Gateway managed; REST and HTTP APIs; tight Lambda + Cognito integration
AWS ALB L7 LB; cheaper than API GW; lacks rate limit / API key features
Nginx self-hosted; powerful but config-heavy
Kong open source + enterprise; plugin model
Envoy building block — most modern gateways embed it
Traefik K8s-friendly, auto-discovery from labels
Cloudflare CDN + edge gateway + DDoS
GCP API Gateway / Apigee GCP-native

Gateway vs Service Mesh

Gateway Mesh
Traffic direction north-south (client → cluster) east-west (service → service)
Auth end-user authentication (JWT, OAuth) service identity (mTLS)
Tenancy per API surface per workload
Location edge per pod

They’re complementary. The gateway handles “is this user allowed to call our API”; the mesh handles “is the orders service allowed to call the payments service”.

Routing patterns

Path-based

/v1/orders/*   → orders-service
/v1/users/*    → users-service
/v1/payments/* → payments-service

Clean, simple. The client sees one API; backend can refactor freely.

Host-based

api.example.com   → public API gateway
admin.example.com → admin gateway with different auth
ws.example.com    → websocket gateway with sticky sessions

Header-based

X-API-Version: 2  → v2 cluster
                  → v1 cluster (default)

Used for canary rollouts where 1% of traffic goes to v2.

Authentication at the gateway

The gateway validates the user’s token (JWT signature, expiry, audience, issuer). On success, it strips the raw token and forwards an inward header the backend trusts:

inbound:  Authorization: Bearer eyJ...   (untrusted)
              ↓ (gateway validates JWT)
inward:   X-User-Id: 12345
          X-User-Roles: admin,billing
          X-Tenant-Id: acme-corp

Backend services trust the inward headers because they only accept traffic from the gateway (network policy / mTLS).

Gotcha: if the inward headers can be set by external clients, you have an auth bypass. Strip X-User-* headers on inbound at the gateway:

location / {
    proxy_set_header X-User-Id "";   # strip incoming
    auth_request /auth/validate;
    auth_request_set $user_id $upstream_http_x_user_id;
    proxy_set_header X-User-Id $user_id;
    proxy_pass http://backend;
}

Rate limiting at the gateway

Centralize per-API-key / per-user / per-IP limits. Backend services don’t each implement it.

# AWS API Gateway usage plan
plan:
  throttle: { rateLimit: 1000, burstLimit: 2000 }   # requests/sec
  quota:    { limit: 1000000, period: MONTH }

Algorithms: token bucket (burst-friendly), sliding window (uniform), fixed window (cheap but bursty at boundaries). See 13_architecture_design/ rate limiting file.

Aggregation / Backend-for-Frontend (BFF)

When a mobile or web client needs data from 3 services, doing 3 HTTP calls from the client is slow. The BFF pattern is a gateway tailored to one client type that aggregates:

# inside the BFF gateway
@app.get("/home")
async def home(user_id: int):
    async with httpx.AsyncClient(timeout=2.0) as c:
        profile, orders, notifs = await asyncio.gather(
            c.get(f"http://users/{user_id}"),
            c.get(f"http://orders?user={user_id}&limit=5"),
            c.get(f"http://notifications/{user_id}/unread"),
        )
    return {
        "profile": profile.json(),
        "recent_orders": orders.json(),
        "unread": notifs.json()["count"],
    }

The BFF can use partial failures gracefully: if notifications is down, still return profile + orders with unread: null.

Anti-patterns

  • Logic in the gateway. Routing + auth + rate limit = OK. Order calculation, business rules = no. Gateway turns into the new monolith.
  • One gateway for everything. Mobile / web / partner APIs have different needs. A BFF per client class is fine; a single one-size gateway forces compromises.
  • Skipping the gateway internally. “It’s an internal service, I’ll just call it.” Defeats the auth/rate-limit/observability story. Use the mesh or accept the gateway.
  • N-deep gateways. Gateway → gateway → service. Each hop adds latency. One edge gateway is enough.

Failure modes

  • Gateway is a SPOF. Run at least 2 zones; for AWS, API GW is regional with multi-AZ built in.
  • Gateway tightly coupled to backends. Routing rules per service mean every service rename is a gateway change. Use service discovery (Cloud Map, K8s Service DNS) rather than hardcoded IPs.
  • Cache invalidation. Edge cache + frequent updates → users see stale data. Use short TTLs + Cache-Control: no-store on dynamic responses, or vary by user.

On AWS

The common stack:

client → CloudFront (CDN, WAF) → API Gateway (auth via Cognito, rate limit) →
       → ALB → ECS / Lambda / EKS

API Gateway can call Lambda directly (no ALB needed), validate JWTs against Cognito User Pools natively, and integrate with WAF.

For internal-only APIs, ALB is often enough and cheaper. API Gateway shines when you need API keys, usage plans, request validation, or direct Lambda integration.

Interview angle

  • “What does an API gateway do?” — single edge entry point handling TLS, auth, rate limit, routing, request shaping, optional aggregation. Pulls those concerns out of every service.
  • “Gateway vs service mesh?” — gateway is north-south (client ↔ cluster), mesh is east-west (service ↔ service). They’re complementary, not competitors. Auth at the gateway authenticates end users; mTLS in the mesh authenticates services.
  • “What’s the BFF pattern?” — Backend-for-Frontend; a gateway tailored to one client type (mobile vs web vs partner). Aggregates calls, shapes payloads. Avoids one-size-fits-all API compromises.
  • “How does the gateway communicate user identity to backends?” — validates the token, then injects inward headers like X-User-Id. Backends trust those headers because they only accept gateway traffic (mTLS / network policy). External headers of the same name must be stripped at ingress.
  • “What goes wrong if you put business logic in the gateway?” — it becomes a shared monolith again — every team blocked on gateway changes, harder to test in isolation, single deploy unit for unrelated features.