What you'll learn
Quick Answer
An API gateway is a single service between clients and your backend services. Every request goes through it, and it handles the concerns that would otherwise be duplicated in every service: routing a request to the right backend, checking authentication, enforcing rate limits, terminating TLS, and sometimes combining several backend calls into one response. The trade-off is one extra network hop, one more thing to deploy, and a single point of failure unless you run it redundantly.
The problem: clients talking to many services
In a microservices setup a single screen might need four services - users, orders, inventory, recommendations - on four different hosts. Without a gateway, the browser or mobile app has to know all four URLs, implement authentication four times, deal with CORS for four origins, make four round trips over the public internet, and break whenever a service is split, moved, or renamed.
Every client type - web, iOS, Android, a partner integration - repeats that same work independently, so a change to how auth tokens are passed means shipping four apps. And cross-cutting concerns like rate limiting and request logging get copy-pasted into all four services, where they slowly drift out of sync until each one enforces slightly different rules.
A gateway collapses this to one URL, one authentication scheme, and one place where the shared logic lives. Clients stop caring how many services sit behind it, what language those services are written in, or where they run. Backend teams can split a service in two without a single client noticing, as long as the gateway keeps routing the same paths.
What a gateway actually does
The core jobs: route each request to the right upstream by path or host; validate the auth token once and reject bad requests before they reach any backend; enforce rate limits; terminate TLS so internal traffic can be plain HTTP on a private network; and give you one place to log every request and attach a trace ID.
const routes = [
{ prefix: '/api/users', target: 'http://localhost:5001' },
{ prefix: '/api/orders', target: 'http://localhost:5002' },
];
const gateway = http.createServer(async (req, res) => {
// one auth check for everything behind the gateway
if (req.headers['authorization'] !== 'Bearer demo-token') {
res.statusCode = 401;
return res.end(JSON.stringify({ error: 'missing or bad token' }));
}
const route = routes.find((r) => req.url.startsWith(r.prefix));
if (!route) { res.statusCode = 404; return res.end('{"error":"no route"}'); }
const upstream = await getJSON(route.target + req.url.replace('/api', ''));
res.statusCode = upstream.status;
res.end(JSON.stringify(upstream.body));
});no token -> 401 {"error":"missing or bad token"}
routed /users -> 200 {"id":7,"name":"Asha"}
routed /orders -> 200 [{"id":"A1","total":900},{"id":"A2","total":250}]This is a teaching version. In production you configure an existing gateway - nginx, Envoy, Kong, Traefik, or a managed one like AWS API Gateway - rather than hand-writing one.
Aggregation and the backend-for-frontend
A gateway can also combine calls. The client hits one endpoint, the gateway fans out to several services, merges the results, and returns a single response - saving a mobile client several slow round trips:
if (req.url === '/api/dashboard?user=7') {
const [user, orders] = await Promise.all([
getJSON('http://localhost:5001/users/7'),
getJSON('http://localhost:5002/orders?user=7'),
]);
return res.end(JSON.stringify({
name: user.body.name,
orderCount: orders.body.length,
lifetimeValue: orders.body.reduce((sum, o) => sum + o.total, 0),
}));
}aggregated -> 200 {"name":"Asha","orderCount":2,"lifetimeValue":1150}When different client types need different response shapes, this becomes the Backend for Frontend pattern: a dedicated gateway per client, so the mobile app gets a lean payload and the web app a richer one without a single gateway trying to serve both. Keep aggregation to fetching and reshaping only - no business rules, no writes with side effects. A gateway that grows business logic becomes the worst part of a distributed monolith.
The gotcha: one slow backend drags down the whole response
Naive aggregation with Promise.all fails entirely if any one upstream fails. Your dashboard shows nothing because the recommendations service - the least important part of the page - happens to be down.
// users service is healthy; orders service is DOWN
async function naiveDashboard() { // Promise.all
const [user, orders] = await Promise.all([userCall(), ordersCall()]);
return { name: user.name, orderCount: orders.length };
}
async function resilientDashboard() { // Promise.allSettled + timeout
const [user, orders] = await Promise.allSettled([userCall(), ordersCall()]);
return {
name: user.status === 'fulfilled' ? user.value.name : null,
orders: orders.status === 'fulfilled' ? orders.value : 'unavailable',
};
}naive -> FAILED: ECONNREFUSED (whole dashboard is now a 500)
resilient -> { name: 'Asha', orders: 'unavailable' }Rules for gateway aggregation: give every upstream call a short timeout, treat each as independently failable, and return partial data with a clear marker instead of a 500. This is also exactly where a per-upstream circuit breaker belongs.
What a gateway costs you
Single point of failure. If the gateway is down, everything is down. Run several instances behind a load balancer and keep it stateless.
Latency. Every request now takes one more network hop and possibly one more TLS handshake. Usually single-digit milliseconds, but it is not zero, and a gateway doing heavy transforms can become the bottleneck.
An organisational chokepoint. If every team needs the gateway team to change a config before they can ship, the gateway becomes everyone's blocker. Favour declarative, version-controlled route config that teams can change by pull request.
Also: developers now need the gateway running locally too, or a documented way to bypass it. For a single backend service you do not need a gateway at all - a reverse proxy like nginx for TLS and rate limiting is enough. The pattern earns its keep once you have several services and more than one kind of client.
