You have split your system into eight services: users, orders, payments, inventory, search, notifications, reviews, recommendations.
Now a mobile app needs to talk to them. Without a gateway, the app must know all eight hostnames, implement authentication against each, handle eight sets of retries and timeouts, and be redeployed every time you move a service.
Worse, every one of those services must independently implement authentication, rate limiting, logging and TLS. Eight teams solving the same four problems, slightly differently, with eight opportunities to get security wrong.
An API gateway is the single front door that fixes both halves of that.
Diagram, as text
- Mobile leads to API Gateway
- Web leads to API Gateway
- Third party leads to API Gateway
- API Gateway leads to Users
- API Gateway leads to Orders
- API Gateway leads to Payments
- API Gateway leads to Search
What belongs in it
The gateway owns concerns that are identical for every service and should therefore be implemented once.
Authentication. Validate the token at the edge, then pass a verified identity inward. Services trust the gateway and stop reimplementing token parsing.
Rate limiting. Enforce quotas before requests consume backend capacity — as covered in the rate-limiting lesson, rejecting at the edge is what makes it cheap.
Routing. Map public paths to internal services. /api/orders reaches the order service wherever it currently runs.
TLS termination. Certificates in one place.
Request and response shaping. Strip internal fields, add correlation IDs, translate between protocols — REST outside, gRPC inside.
Observability. Every request passes through one point, so you get consistent logs, latency metrics and error rates across the whole API without instrumenting each service identically.
Aggregation, and its trade-off
A mobile home screen needs profile, recent orders, recommendations and unread notifications — four services. On a mobile network, four round trips at 150ms each is a slow screen.
A gateway can fan out internally and return one response:
app.get('/api/home', async (req, res) => {
const [profile, orders, recs, notes] = await Promise.allSettled([
users.getProfile(req.userId),
orders.recent(req.userId, 5),
recommendations.forUser(req.userId),
notifications.unread(req.userId),
]);
res.json({
profile: value(profile),
orders: value(orders),
recommendations: value(recs) ?? [], // degrade rather than fail
unread: value(notes) ?? 0,
});
});One client round trip; four parallel calls inside the datacentre where latency is sub-millisecond.
Note allSettled rather than all. If recommendations are down, the home screen should still render with the other three sections. Partial degradation beats total failure, and the gateway is the natural place to decide that.
Backend-for-frontend
The refinement that solves the "one gateway, many very different clients" problem.
A mobile app wants small payloads and few round trips. A desktop web app wants richer data. A partner API wants stability above all. Serving all three from one gateway means constant compromise, and every client's needs leak into shared config.
Instead, give each client type its own thin gateway:
Diagram, as text
- Mobile app leads to Mobile BFF
- Web app leads to Web BFF
- Partners leads to Partner API
- Mobile BFF leads to Shared services
- Web BFF leads to Shared services
- Partner API leads to Shared services
Each BFF is owned by the team that owns that client, and shapes responses exactly for it. Mobile can change its payload without risking the partner API.
The cost is more moving parts. Worth it when clients genuinely differ; overkill when they do not.
The risks you must name
It is a single point of failure. Everything flows through it. Run multiple instances behind a load balancer — the same reasoning as every other shared component.
It adds latency. One extra network hop on every request. Usually a few milliseconds, and worth it, but not free.
It can become an operational bottleneck. If every team must file a change request against gateway config to ship a route, you have created a queue. Prefer declarative, service-owned configuration.
Debugging gets harder. Requests now traverse an extra layer that can rewrite headers, time out independently, and cache. This is exactly why correlation IDs, from the logging lesson, matter so much here.
Where it sits
A common full picture, with each layer doing one job:
DNS -> Load balancer -> API gateway -> Services- DNS resolves the name and can route regionally.
- The load balancer spreads traffic across gateway instances and handles failover.
- The gateway authenticates, limits, routes and observes.
- Services implement business logic and trust that the edge did its job.
Not every system needs all four. A single application behind a reverse proxy is perfectly reasonable — introduce a gateway when you have several services and several clients, which is the point at which duplicated cross-cutting concerns start to hurt.
Check your understanding
Common mistakes
Business logic in the gateway. Discounts, eligibility rules and workflow belong in services. Config files are hard to test, review and debug.
One gateway for wildly different clients. That is what BFFs are for.
No timeout or circuit breaker on backend calls. A slow service holds gateway connections open and the failure spreads outward instead of being contained.
Forgetting internal traffic. Services calling each other usually should not go back out through the public gateway — that adds latency and a dependency on your own edge.
Treating it as a security boundary on its own. Services should still validate input and check authorisation. If an attacker reaches your internal network, a gateway that already waved them through protects nothing.
Practice
Your API gateway now contains request transformation, response caching, three teams' routing rules, and some business logic that crept in. Deploys are risky and every team is blocked on the gateway team. Diagnose the problem and describe how you would unwind it.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Name what the gateway has become, not just what is wrong with it.
Show a strong answer
The diagnosis: the gateway has become a shared, centrally-deployed component that every team must change in order to ship — the same coupling service boundaries were meant to remove, relocated to the edge. The symptom is organisational (everyone blocked on one team) but the cause is technical: logic that varies per service is living in a component shared by all services.
How to unwind it, in order of value:
- Move business logic back into services. Anything that needed to know what a record means was never a gateway concern. This is the highest-risk code in the worst place.
- Make routing declarative and owned by the service. Each service registers its own routes through configuration in its own repository, so adding an endpoint is not a gateway deploy. This alone removes most of the blocking.
- Keep only what is genuinely uniform: authentication, rate limiting, TLS termination, request logging and routing. These benefit from being in one place because they should behave identically for everything.
- Split request transformation per client, not per service. If the transformations exist because mobile and web want different shapes, that is a backend-for-frontend, and each should be owned and deployed by the team that owns that client.
The test to apply to anything remaining: would every service want this to behave identically? If yes, it belongs at the edge. If it varies, it belongs where it varies.
Design the failure behaviour of an aggregating endpoint that composes a user profile from an identity service (essential), a preferences service (nice to have) and a recommendations service (decorative). Specify timeouts and what the client receives in each failure case.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Rank the dependencies by what the page cannot render without.
Show a strong answer
Rank the dependencies first, because that decides everything else.
- Identity — essential. Timeout 500 ms, one retry on a connection failure only. If it fails, the endpoint fails: a profile page without the profile is not a degraded response, it is a wrong one.
- Preferences — nice to have. Timeout 200 ms, no retry. On failure, return the profile with preferences absent and a flag saying so, and let the client fall back to defaults. Cache the last known good value and serve that if available — stale preferences are far better than none.
- Recommendations — decorative. Timeout 100 ms, no retry, circuit breaker that opens quickly. On failure, omit the section entirely and let the client render without a gap.
Two rules that make this work:
- A total budget, not just per-call budgets. The three calls run in parallel, so latency is the slowest essential call rather than the sum — but the endpoint still needs its own overall deadline, say 800 ms, after which it returns whatever it has.
- The response says what is missing. A field absent because the service failed must be distinguishable from a field absent because the user has none. Otherwise the client cannot tell "no preferences set" from "we could not find out", and will overwrite real data with defaults.
Key takeaways
- A gateway gives clients one front door and stops every service reimplementing auth, rate limiting, TLS and logging.
- It is API-aware in a way a plain reverse proxy is not — clients, tokens, quotas, versions and per-endpoint policy.
- Aggregation cuts client round trips, but use
allSettledso one failing dependency degrades a section rather than the whole response. - Heavy aggregation turns the gateway into a new monolith on the critical path. Push client-specific composition into a backend-for-frontend.
- Name the risks: single point of failure, extra latency, an organisational bottleneck, harder debugging.
- Introduce one when you have several services and several clients — not on day one.
Next: some content should never reach your servers at all, because the fastest request is the one answered closest to the user.
Further reading
- Chris Richardson, Microservices Patterns (Manning, 2018), chapter 8 — the API gateway and backend-for-frontend patterns.
- Sam Newman, Building Microservices, second edition (O'Reilly, 2021), chapter 5 — the risks of putting logic in shared edge components.
- Phil Calçado, "The Back-end for Front-end Pattern (BFF)" (SoundCloud engineering, 2015) — where the pattern was named.
- Michael Nygard, Release It!, second edition (Pragmatic Bookshelf, 2018) — timeouts, bulkheads and circuit breakers for aggregation.
- Google SRE, Site Reliability Engineering (O'Reilly, 2016), chapter 21 — handling overload at the edge.