An API gateway is the single front door that request traffic passes through before it reaches your services — the place to put the concerns that don't belong in any one service but every service needs: authentication, rate limiting, routing, and request/response transformation. Get the pattern right and it disappears into the infrastructure. Get it wrong and it becomes a second monolith, a shared chokepoint every team has to coordinate a deploy through.
Edge gateway vs. backend-for-frontend
The single biggest pattern decision is whether you run one shared gateway for all clients or a BFF (backend-for-frontend) per client type. A single edge gateway is simpler to operate — one set of routes, one auth policy, one place to look for logs — but it accumulates client-specific logic over time as the mobile team and the web team both need slightly different response shapes, and that logic tends to pile up in gateway configuration nobody owns. A BFF per client (one gateway for the mobile app, one for the web app, one for partner integrations) keeps that shaping logic close to the team that needs it, at the cost of running more gateway instances and duplicating the auth/rate-limit plumbing across them. Teams with a handful of well-differentiated client types tend to land on BFFs; teams with one dominant client type usually don't need the split.
Core cross-cutting responsibilities
The concerns that belong at the gateway are the ones that are identical across every backend service: TLS termination, authentication (validating a JWT or API key before the request reaches a service that shouldn't have to know how auth works), rate limiting, and request logging with a correlation ID injected on the way in. The concerns that don't belong at the gateway are business logic and anything that requires knowledge of a specific domain — a gateway that decides pricing rules or validates business invariants has stopped being infrastructure and started being an undocumented second implementation of your service.
If moving from Kong to Envoy (or vice versa) would require rewriting business logic, the gateway absorbed responsibilities it shouldn't have. A well-scoped gateway config is annoying to lose but never a multi-quarter migration.
Routing and aggregation patterns
Simple path-based routing (/orders/* to the orders service, /users/* to the users service) covers most cases and is easy to reason about. The pattern that gets more use than it should is request aggregation — the gateway fans a single client request out to two or three backend services and composes the response, saving a mobile client several round trips. This is genuinely useful for high-latency client networks, but it's also where gateways quietly grow business logic: composing three responses into one requires deciding what happens when one of the three fails, and that decision is domain logic wearing an infrastructure costume.
services:
- name: orders-service
url: http://orders.internal:8080
routes:
- name: orders-route
paths: ["/api/orders"]
plugins:
- name: rate-limiting
config:
minute: 100
policy: local
- name: jwt
config:
claims_to_verify: ["exp"]
Rate limiting and circuit breaking at the edge
Rate limiting belongs at the gateway because it needs to happen before a request consumes any backend capacity — limiting inside the service means the expensive work (a DB connection, a query) already happened before you reject the request. The two common algorithms are token bucket (allows bursts up to a cap, then throttles to a steady rate) and sliding window (smoother, more accurate, slightly more expensive to compute). Circuit breaking is the complementary pattern: when a backend starts failing or timing out, the gateway stops sending it traffic for a cooldown period rather than piling retries onto a service that's already struggling.
Because every request passes through it, the gateway is the single point of failure for the entire API surface, even if every backend service is healthy. Run it across multiple availability zones and load-test its own capacity limits, not just the backends behind it.
When a gateway is the wrong tool
Not every cross-cutting concern belongs at the edge. Service-to-service traffic inside a cluster is usually better served by a service mesh sidecar than by routing everything back out through a central gateway — round-tripping internal calls through the edge adds latency and makes the gateway a bottleneck for traffic it was never meant to carry. The rule of thumb: gateway for north-south traffic (client to service), mesh or direct calls for east-west traffic (service to service).
| Pattern | Solves | Watch for |
|---|---|---|
| Single edge gateway | Simple ops, one auth policy | Client-specific logic piling up in config |
| BFF per client type | Client-tailored responses | Duplicated plumbing across BFFs |
| Request aggregation | Fewer round trips for slow clients | Business logic creeping into the gateway |
| Edge rate limiting | Protects backend capacity | Needs to be zone-aware for accuracy |
Wrapping up
A gateway earns its place by staying boring: routing, auth, rate limiting, and logging, applied uniformly, with nothing a service team would need to file a ticket to change. The moment it starts making domain decisions, it's no longer infrastructure — it's an undocumented service every other service now depends on.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.