Filter concepts by levelShowing all levels.

System Design · Section 11

Client-Server and API Gateways

Level
intermediate
Read
18 min
Concepts
3

A request from a browser or mobile client rarely reaches a backend service directly — it typically passes through a load balancer and an API gateway (or reverse proxy) first. This section names each layer's job and the responsibilities — routing, authentication, rate limiting, transformation, aggregation, observability — an API gateway can centralize, and the one thing it must not: business logic.

System Design overview

What is true here

  1. Client → load balancer → API gateway (or reverse proxy) → backend service is the typical path.
  2. A gateway can centralize routing, auth, rate limiting, request/response transformation, aggregation and observability.
  3. Aggregation lets a client make one call where it would otherwise need several.
  4. Business logic belongs in the owning service, never in the gateway.

What you will be able to do

  • Name each layer between a client and a backend service and what job it does
  • List what an API gateway can centralize on behalf of every backend service
  • Recognize when a gateway has been overloaded with business logic that belongs elsewhere

The request path

Who sits between a client and the backend service that actually runs the logic, what a gateway can centralize, and the one thing it must never own.

Client-server topology

corebeginner

A request from a browser or mobile app rarely hits a backend service directly. It usually passes through a load balancer, then a reverse proxy or API gateway, before reaching the service that actually handles it — each layer has a distinct job.

Think of it as

Think of visiting a large office building. You do not walk straight to the employee who handles your request. You go through the front door (load balancer, picking which entrance is least busy), the reception desk (API gateway, checking your ID and routing you to the right department), and sometimes an internal assistant (reverse proxy) before you reach the actual desk (backend service) that does the work.

text
client → load balancer → API gateway → backend service(s)
                              ↳ reverse proxy in front of any of them

What we're doing: Trace one request through every layer between a mobile app and the service that handles it.

request-path.txttext
Mobile app calls: POST /api/checkout

1. DNS resolves api.example.com to a load balancer IP.
2. Load balancer picks one of 6 healthy API gateway instances.
3. API gateway validates the auth token, checks the caller's
   rate limit, and routes /api/checkout to the checkout service.
4. The checkout service (one of 12 running instances, chosen
   by an internal load balancer) executes the order logic.
5. The response travels back through the same chain.
3
The load balancer only picks an instance — it does not know what an "order" is.
5
The gateway is the layer that knows about auth and rate limits — the backend service does not repeat that logic.
8
Internal traffic gets load-balanced too — this is not a client-facing-only concern.

Why this works: Naming each layer explicitly is what lets a design conversation say precisely where a responsibility (auth, routing, rate limiting) lives, rather than leaving it ambiguous which component owns it.

Treating "the backend" as a single box in a design doc

Wrong

text
Client → Backend → Database

Better

text
Client → Load balancer → API gateway →
Backend service (N instances) → Database

What you see: A design review cannot answer "where does auth happen" or "what happens if one instance dies" because the diagram collapsed four distinct components into one box.

Why: Each layer fails differently and owns a different responsibility. Collapsing them hides exactly the questions a system design review needs to ask.

One request, every layer it passes through

Client

browser or mobile app

Load balancer

picks a healthy instance

API gateway

auth, routing, rate limit

Backend service

runs the business logic

  • Client — browser or mobile app
    • leads to Load balancer
  • Load balancer — picks a healthy instance
    • leads to API gateway
  • API gateway — auth, routing, rate limit
    • leads to Backend service
  • Backend service — runs the business logic

Who sits between the client and the code that runs

Who sits between the client and the code that runs
ComponentSeesTypical job
ClientThe userSends requests, renders the response
Load balancerEvery incoming connectionPicks which backend instance handles it
API gatewayEvery API callAuth, routing, rate limiting, one entry point
Reverse proxyRequests for one or more servicesForwards, may cache or terminate TLS
Backend serviceOne request at a timeRuns the actual business logic

Together

text
GET /api/orders/42
  → Load balancer   (picks a healthy gateway instance)
  → API gateway     (checks auth token, applies rate limit)
  → Reverse proxy   (routes /api/orders/* to the orders service)
  → Orders service  (reads from its database, returns JSON)

Remember: A request usually passes through a load balancer, an API gateway (or reverse proxy), and only then a backend service instance — four distinct jobs, not one box.

See also: api gateway responsibilities · l4 vs l7

API gateway responsibilities

coreintermediate

An API gateway is the single front door for a set of backend services. It can handle routing, authentication, rate limiting, request/response transformation, aggregating multiple backend calls into one response, and observability — so every backend service does not reimplement the same cross-cutting concerns.

Think of it as

Think of an API gateway as airport security and the check-in desk combined, before every passenger reaches any individual gate. It checks your ticket (authentication), decides which gate you go to (routing), limits how many people go through per minute (rate limiting), and can hand you a combined boarding pass covering a connecting flight (aggregation) — none of which the gate agents at each individual flight need to redo.

text
client → [API gateway: auth, rate limit, route,
                      transform, aggregate, log]
                → backend service(s)

What we're doing: Show a gateway centralizing auth and aggregation for a mobile dashboard that would otherwise need three separate calls.

gateway-aggregation.txttext
Without a gateway, the mobile app makes 3 calls:
  GET user-service.internal/profile
  GET orders-service.internal/recent
  GET billing-service.internal/balance
Each over a separate mobile network round trip.

With an API gateway:
  GET api.example.com/dashboard
    → gateway checks the auth token once
    → gateway calls all three internal services
    → gateway merges the three JSON bodies into one
  Mobile app makes 1 call, 1 round trip, gets everything.
6
The client no longer needs to know three internal service hostnames.
7
Auth is checked exactly once at the gateway, not three times by three services.
9
One round trip over a mobile network is the entire point — three round trips on a slow connection is visibly slower.

Why this works: Aggregation at the gateway trades three client round trips (each paying full mobile-network latency) for one, and moves auth/rate-limiting out of every individual service into a single, auditable place.

Letting every backend service implement its own auth check

Wrong

text
// repeated, slightly differently, in every service
if (!validateToken(req.headers.authorization)) {
  return res.status(401).send();
}

Better

text
// gateway validates once; services trust a
// signed, gateway-attached identity header
const userId = req.headers['x-verified-user-id'];

What you see: Each service's auth check drifts slightly out of sync over time (different token formats, different error codes), and a security fix has to be deployed to every service instead of one gateway.

Why: Duplicated cross-cutting logic diverges. Centralizing it at the gateway means one fix, one place to audit, and services that trust an already-verified identity instead of re-implementing verification.

One gateway call, aggregated from three services

Web

Mobile

API gateway

auth, rate limit, route, aggregate

user-service

profile

orders-service

recent orders

billing-service

account balance

  • Web
    • leads to API gateway
  • Mobile
    • leads to API gateway
  • API gateway — auth, rate limit, route, aggregate
    • leads to user-service
    • leads to orders-service
    • leads to billing-service
  • user-service — profile
  • orders-service — recent orders
  • billing-service — account balance

What an API gateway can centralize

What an API gateway can centralize
ResponsibilityWithout a gatewayWith a gateway
AuthEvery service re-checks tokensChecked once, at the edge
Rate limitingEach service tracks its own limitsOne shared limiter per client/key
RoutingClient must know every service addressClient calls one host; gateway routes
AggregationClient makes N calls, N round tripsGateway makes N calls, client makes 1
ObservabilityLogs scattered across N servicesOne place to see every call in/out

Together

text
GET /api/dashboard  (one client call)

Gateway internally calls:
  → user-service     (profile)
  → orders-service    (recent orders)
  → billing-service   (account balance)

Gateway combines the three responses into one
JSON payload before returning to the client.

Remember: A gateway can own routing, auth, rate limiting, transformation, aggregation and observability — but never the business logic itself.

See also: client server topology · l4 vs l7

Do not overload the gateway with business logic

standardintermediate

An API gateway can own routing, auth, rate limiting, transformation, aggregation and observability — but the moment it starts encoding an actual business rule ("reject orders over the customer's credit limit"), it has become a second, hidden application every backend service now silently depends on.

Think of it as

A receptionist can check your ID, tell you which floor to go to, and even staple your documents together before passing them on — all legitimate front-desk work. The moment the receptionist starts deciding whether your loan application should be approved, they have quietly taken over a decision that belongs to the loan department, and now the loan department is not the only place that decision can happen.

text
gateway may:      route, authenticate, rate-limit,
                  transform, aggregate, log
gateway may not:  decide anything about what a domain
                  entity (order, account, inventory) is
                  allowed to do

What we're doing: Show a credit-limit rule migrating from the gateway (where it does not belong) to the owning service.

business-logic-migration.txttext
Before: gateway rejects large orders itself
  gateway.use((req, res, next) => {
    if (req.body.total > req.user.creditLimit) {
      return res.status(402).send('over credit limit');
    }
    next();
  });
  // the orders service never even sees a rejected
  // order, and has no idea this rule exists

After: gateway only routes; orders service decides
  gateway.route('/orders/*', ordersService);
  // inside ordersService:
  if (order.total > customer.creditLimit) {
    return rejectOrder(order, 'over_credit_limit');
  }
  // the rule lives exactly once, in the service
  // that owns "orders" and "credit limits"
3
This looks convenient — one place to enforce the rule — but it means the orders service is no longer the source of truth for its own domain.
15
Moving the rule here means the orders service can be tested, deployed and understood without needing to know the gateway exists.

Why this works: A rule enforced at the gateway is invisible to the service it is actually about — anyone reading the orders service's code sees no credit-limit check at all, because it silently happens somewhere upstream.

Adding "just one" domain rule to the gateway because it is convenient

Wrong

text
// gateway middleware, "just this one check"
if (req.body.total > req.user.creditLimit) {
  return res.status(402).send();
}

Better

text
// gateway stays a pure router; the check moves
// into the orders service, where the concept
// of "credit limit" and "order total" actually
// live and can be tested together

What you see: Over time, the gateway accumulates a growing list of domain-specific checks that nobody remembers adding, each service's own tests pass without exercising those rules at all, and no single service's codebase actually contains the full picture of its own business logic.

Why: Every "just one" exception erodes the boundary a little more. A gateway that started as a pure routing layer gradually becomes a second, undocumented application layer that every backend service depends on without knowing it.

The line the gateway must not cross

A gateway may own this

  • +Route a path to the service that owns it
  • +Authenticate the caller and terminate TLS
  • +Rate-limit, retry, and shed load
  • +Transform and aggregate responses
  • +Log, trace, and emit metrics
  • +None of these needs to know what an order is

A gateway may not own this

  • Any rule about what a domain entity is allowed to do
  • Anything that reads an order total, a credit limit, an inventory count
  • A check the owning service cannot see, test, or deploy with
  • The test: if the rule names a domain concept, it is business logic
  • A gateway may own this
    • Route a path to the service that owns it
    • Authenticate the caller and terminate TLS
    • Rate-limit, retry, and shed load
    • Transform and aggregate responses
    • Log, trace, and emit metrics
    • None of these needs to know what an order is
  • A gateway may not own this
    • Any rule about what a domain entity is allowed to do
    • Anything that reads an order total, a credit limit, an inventory count
    • A check the owning service cannot see, test, or deploy with
    • The test: if the rule names a domain concept, it is business logic

Remember: A gateway can own routing, auth, rate limiting, transformation, aggregation and observability — never a rule about what a domain entity is allowed to do. That belongs in the service that owns the entity.

See also: api gateway responsibilities · layered architecture

Advertisement