Put one front door in front of a set of services, handling routing, auth, and rate limiting so clients never talk to services directly.
If a client has to know the network address of every microservice, authenticate against each one separately, and handle each service's own rate limits, every service's internal reorganization becomes a breaking client change — the boundary between "internal structure" and "public contract" disappears.
Every external request goes through the gateway first. It authenticates the request once, applies rate limiting, and routes to the correct internal service based on the path — clients only ever know the gateway's address, never the services behind it.
The client only calls the gateway. Which internal service actually handles the request is an internal routing decision, invisible to the caller.
const services = {
"/orders": (path) => `orders-service handled ${path}`,
"/users": (path) => `users-service handled ${path}`,
"/billing": (path) => `billing-service handled ${path}`,
};
function authenticate(request) {
if (!request.token) throw new Error("401 unauthorized");
}
function gateway(request) {
authenticate(request);
const prefix = "/" + request.path.split("/")[1];
const handler = services[prefix];
if (!handler) return "404 not found";
return handler(request.path);
}
console.log(gateway({ token: "abc", path: "/orders/42" }));
console.log(gateway({ token: "abc", path: "/billing/invoice-1" }));