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-storeon 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.