The problem: clients know too much
With no gateway, a mobile app opens connections to catalog, orders, payments, search and profile directly. That means the app hard-codes five hostnames, implements retry policy five times, and has to ship an update whenever you split a service in two.
Meanwhile each service has written its own JWT verification, its own rate limiter and its own request logging — five implementations, of which at least one has a bug.
What belongs in a gateway
| Concern | Why the edge | Notes |
|---|---|---|
| TLS termination | One certificate lifecycle | Re-encrypt internally if required |
| Authentication | Reject unauthenticated early | Validate token, pass verified claims |
| Routing | Clients address paths, not hosts | Enables service splits without client releases |
| Rate limiting | Protects everything behind it | Per key, per IP, per route |
| Request/response shaping | Header hygiene, compression | Keep it mechanical |
| Observability | One place with the whole picture | Trace id injection belongs here |
| Canary and A/B routing | Traffic splitting without redeploys | Weighted routes |
What must never go in it
The gateway is shared infrastructure. Anything you put in it becomes a deployment dependency for every team.
- Business logic. Discount rules in a Lua filter is how you get an outage during a sale that nobody can debug.
- Authorisation decisions. The gateway can authenticate — prove who you are. Whether user X may refund order Y depends on domain state the gateway does not have.
- Data transformation with domain meaning. Renaming a field is fine; deriving totals is not.
- Long-running work. Anything slow at the edge consumes connections that all traffic shares.
Backend for Frontend
One gateway serving a web app, a mobile app and partner integrations eventually becomes a pile of client-specific conditionals. The BFF pattern gives each client class its own thin gateway.
- Mobile BFF returns compact payloads and aggregates aggressively to save round trips.
- Web BFF returns richer objects and can rely on better connectivity.
- Partner API is versioned, contractual, and changes at a very different pace.
Each BFF is owned by the client team, which removes the cross-team queue that a single shared gateway creates.
Aggregation and its trap
Combining three calls into one is genuinely valuable on mobile networks, where a round trip can cost 200ms. But an aggregating endpoint inherits the availability of everything it calls.
GET /bff/home
├─ catalog.featured() required
├─ profile.summary() required
└─ reco.forUser() optional, 150ms budget
// three sequential calls: 90 + 40 + 150 = 280ms
// parallel with per-call budgets: max(90, 40, 150) = 150ms
return {
featured, profile,
recommendations: recoResult ?? null, // degrade, do not fail
degraded: recoResult ? [] : ["recommendations"],
}Fan out in parallel, give each dependency its own timeout, and mark optional sections as degraded rather than failing the whole response.
Authentication at the edge
- Gateway validates the JWT signature, expiry, issuer and audience.
- It strips any client-supplied identity headers — otherwise a caller can forge
X-User-Id. - It injects verified claims as internal headers, or forwards the token itself.
- Services still verify authorisation. Never assume “internal traffic is trusted”; that assumption fails the day one service is compromised.
Configuration sketch
routes:
- path: /api/orders/*
service: orders-svc
auth: required
timeout: 3s
retries: 0 # non-idempotent, never retry blindly
rateLimit: { key: user, limit: 100, window: 1m }
- path: /api/catalog/*
service: catalog-svc
auth: optional
timeout: 800ms
retries: 1
cache: { ttl: 60s, vary: [locale] }
rateLimit: { key: ip, limit: 1000, window: 1m }
- path: /api/payments/*
service: payments-svc
auth: required
timeout: 10s
retries: 0
rateLimit: { key: user, limit: 10, window: 1m }The single point of failure
Everything now depends on the gateway, so treat it as a tier-0 system.
- Run it stateless and horizontally scaled across zones.
- Keep the config in version control with staged rollout — most gateway outages are config.
- Watch added latency separately from upstream latency; the gateway's own p99 is a metric.
- Do not let it become the deployment bottleneck for every team's route change.
Interview framing
“All clients hit a stateless gateway that terminates TLS, validates the JWT, strips forged identity headers, applies per-user rate limits and injects a trace id. Routing is declarative so we can split services without shipping a client. Authorisation stays in the services because it needs domain state. Mobile gets its own BFF for aggregation with per-dependency timeouts and partial responses.”