Phase 2
API gateways
Use one front door to route requests, check access, and keep backend services simpler.
A gateway is a reverse proxy that understands the API, not just the bytes
A plain reverse proxy, as covered in the proxies topic, forwards traffic and can load-balance, terminate TLS, and cacheCacheA copy of data kept somewhere faster to read from than its original source, so repeated requests don't have to pay the full cost every time. Deliberately allowed to be wrong or empty - a cache miss should never be treated as an error. - all without knowing anything about what's actually inside the requests it's forwarding. An APIAPIA defined way for one piece of code to ask another to do something, without needing to know how it happens internally. Not a specific technology - a function signature, a library's exports, and a REST endpoint are all APIs. gateway sits at the same position in the stack but is deliberately API-aware: it parses request paths, headers, and often payloads, and makes routing and access decisions based on what it finds. A reverse proxy asks "where does this request go." An API gateway asks that plus "is this request even allowed to go there, and does it need to be transformed first."
Concretely, an API gateway typically handles:
- Authentication and authorization - validating a JWT or API key once, at the edge, before a request ever reaches a backend service
- Rate limitingRate limitingDeliberately capping how many requests a client can make in a given time window, to keep a shared system fair and stable instead of letting one client's traffic degrade it for everyone else. - enforcing per-client request limits centrally, rather than each service implementing its own
- Request routing by path -
/orders/*goes to the order service,/users/*goes to the user service,/payments/*goes to the payment service, all behind one public hostname - Request/response transformation - reshaping a request before forwarding it, or aggregating responses from multiple backend calls into one response for the client
- Protocol translation - accepting REST from the public internet and translating to internal gRPC calls, a pattern mentioned in the gRPC topic
Interactive example
One front door, two outcomes
At rest a valid request is routed by path to the order service. Hover to send one that fails the check at the edge.
The gateway verifies the token and checks the rate limit before deciding anything else, then routes by path - /orders to the order service, /users to the user service. A request without a valid token gets a 401 from the gateway itself, and no backend service ever hears about it.
A concrete before/after
Before a gateway, in a microservices setup with an order service, a user service, and a payment service, each service commonly ends up implementing its own auth check:
// order-service - repeated in every service
app.use(async (req, res, next) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "missing token" });
try {
req.user = await verifyJwt(token);
next();
} catch {
res.status(401).json({ error: "invalid token" });
}
});// user-service - the same logic, copy-pasted
app.use(async (req, res, next) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "missing token" });
try {
req.user = await verifyJwt(token);
next();
} catch {
res.status(401).json({ error: "invalid token" });
}
});Same logic, duplicated across every service, and every service needs to be updated in lockstep if the JWT verification logic ever changes - a new required claim, a rotated signing key, a switch to a different token format.
After introducing a gateway, the check happens once, at the edge, and the identity it establishes is passed downstream as a trusted header:
// gateway - runs once, for every request, regardless of destination service
app.use(async (req, res, next) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "missing token" });
try {
const user = await verifyJwt(token);
req.headers["x-user-id"] = user.id; // trusted, gateway-verified identity
next();
} catch {
res.status(401).json({ error: "invalid token" });
}
});// order-service - trusts the gateway, no JWT logic at all
app.get("/orders", (req, res) => {
const userId = req.headers["x-user-id"]; // gateway already verified this
res.json(getOrdersForUser(userId));
});Backend services stop needing to know anything about JWTs, signing keys, or token formats - they trust a header the gateway has already verified, on the assumption that the network between the gateway and backend services is itself trusted (typically a private VPC, sometimes reinforced with mTLS).
Gateway vs load balancer vs reverse proxy, in one line each
A load balancerLoad balancerA component that sits in front of multiple servers and distributes incoming requests across them, so no single machine gets overwhelmed and a crashed instance doesn't take the whole system down. distributes traffic across instances of the same service. A reverse proxy forwards and shapes traffic without understanding the application protocol beyond HTTP itself. An API gateway understands the API - it makes decisions based on paths, tokens, and payloads, and routes to entirely different backend services rather than different replicas of the same one. In practice these roles often collapse into the same product (Kong, AWS API Gateway, or an Nginx/Envoy setup configured with enough rules can do all three), but the roles themselves are distinct even when one tool plays all of them.
Interview prep
This topic comes up in interviews - 3 questions, leveled by role.