Hermes Wiki
Architecture/Fundamentals/api-gateway-pattern

API Gateway Pattern

Concept

Once a system is decomposed into many microservices, a naive client (a mobile app, a web SPA, a third-party integration) would have to know about, discover, and call each service directly. That leaks the internal service topology to every client, forces each client to handle cross-cutting concerns (auth, retries, TLS) itself, and makes the network chatty — a single screen might need calls to a dozen services.

The API gateway pattern (popularized by Chris Richardson's microservices.io) inserts a single entry point between external clients and the internal services. It is the one public front door. Its responsibilities fall into two buckets:

  • Routing / composition — accept a client request and route it to the right internal service, or fan out to several services and aggregate their responses into one payload (API composition), so a client makes one call instead of ten.
  • Cross-cutting concerns, centralized — authentication and authorization, TLS termination, rate limiting and throttling, request/response logging and metrics, caching, request validation, and protocol translation (e.g. external REST/GraphQL → internal gRPC). Doing these once at the edge means each internal service doesn't re-implement them.

The gateway also decouples the client from the internal topology: services can be split, merged, renamed, or relocated behind the gateway without breaking clients, because the gateway presents a stable external contract.

A common refinement is Backend for Frontend (BFF) — instead of one gateway serving all client types, you run a separate gateway per client class (one for mobile, one for web, one for partners), each tailored to that client's needs. See BFF for that specialization; the API gateway is the general pattern, BFF is the "one gateway per experience" variant.

Tradeoffs

Concern With API gateway Without (direct client-to-service)
Client complexity Low — one endpoint, one auth handshake, one call per screen High — clients discover and call each service, handle N auth/retry/TLS setups
Cross-cutting concerns Centralized once at the edge Duplicated in every service or every client
Coupling to topology Loose — internal changes hidden behind a stable contract Tight — any service change can break clients
Failure domain Gateway is a critical shared path; must be HA No single shared choke point
Latency One extra network hop; composition can hide fan-out latency Direct calls, but chatty for multi-service screens

The dominant risk is that the gateway becomes a single point of failure and a scaling/deployment bottleneck — every request flows through it, so it must be horizontally scaled and highly available, and if every team has to modify the gateway to ship a feature, it becomes a coordination chokepoint (the "distributed monolith" smell). The discipline is to keep the gateway thin: routing, auth, and cross-cutting concerns — not business logic. Business logic belongs in the services behind it.

When to use / when not to

  • Use whenever multiple external client types consume a microservices backend and you want to hide internal topology and centralize auth, rate limiting, and TLS.
  • Use when client-side call chatter is a real problem — a gateway doing API composition turns a dozen client round-trips into one.
  • Strongly consider the BFF variant when different clients (mobile vs. web vs. partner API) need materially different payloads and aggregation — a single one-size-fits-all gateway ends up bloated with client-specific logic.
  • Don't add one for a single monolith or a single service — there's nothing to route, compose, or hide, so it's pure overhead and an extra hop.
  • Don't let it accumulate business logic — the moment domain rules live in the gateway, you've built a new monolith that every team must coordinate through.

Common pitfall

Letting the gateway drift from "thin edge concern handler" into "place where business logic lives." It starts innocently — one endpoint needs a small transformation, then a special-case validation, then some orchestration — and soon the gateway encodes domain rules that belong in services. Now every feature requires a gateway change, teams serialize behind it, and a gateway deploy can break unrelated features: you've recreated the monolith you decomposed to escape, plus an extra network hop. The related pitfall is not treating the gateway as a first-class HA component — because everything flows through it, an under-provisioned or single-instance gateway is a system-wide single point of failure.

Principal Engineer Lens

The API gateway is a good test of whether an architect understands the difference between an edge concern and a domain concern — the reviewable question is always "what lives in the gateway, and why isn't it business logic?" A strong design keeps the gateway to routing, authN/authZ, rate limiting, and composition, and can articulate that separation crisply, because that boundary is what keeps team autonomy intact (teams ship services without coordinating on a shared gateway). In Fintech and BigTech contexts the gateway is also the natural security and governance chokepoint — the single place to enforce authentication, mTLS to internal services, request validation, and audit logging on every external call — which is a defensible answer to "where do you enforce access control across all your APIs." The failure mode to name in review is the gateway becoming a distributed-monolith bottleneck; the mitigation (thin gateway + per-client BFFs) is the Principal-level nuance.

Reel Script

Setup: You split your backend into twenty microservices. Now your mobile app needs to render one screen — and to do it, it'd have to call eight of those services directly, each with its own auth, its own retries, its own TLS. And the moment you rename or split a service, the app breaks. That's untenable.

Concept walkthrough: The API gateway is one public front door in front of all the services. It routes each request to the right service — or fans out to several and aggregates the results into one response, so the app makes one call instead of eight. And it centralizes the cross-cutting stuff — auth, rate limiting, TLS termination, logging — so each service doesn't reimplement them. Clients talk to a stable contract; the messy internal topology stays hidden.

Real example tie-in: Walk one screen: without a gateway, eight client round-trips, eight auth handshakes. With a gateway doing composition: one client call, the gateway fans out internally over a fast internal network, stitches the responses, returns one payload. Then show renaming an internal service — clients never notice, because the gateway absorbs it.

Tradeoffs & alternatives: The gateway is now a critical shared path — it must be highly available and horizontally scaled, or it's a system-wide single point of failure. And the big anti-pattern: don't put business logic in it. Keep it to routing, auth, and cross-cutting concerns. When different clients need very different shapes, split into per-client Backends-for-Frontend instead of one bloated gateway.

Principal Engineer takeaway: The reviewable question is "what's in the gateway, and why isn't it business logic?" A gateway that stays thin preserves team autonomy and doubles as your security and audit chokepoint; a gateway that accumulates domain logic becomes a distributed monolith that every team has to coordinate through.

Sources:

Hermes Wiki