Microservices API Gateway Pattern
In modern software architecture, microservices have become a popular way to build scalable and flexible applications. Instead of one large application, you have many small, independent services, each responsible for one business function. But how do clients talk to all of them? The answer is the API Gateway pattern.
This guide explains what an API gateway is, what it does, and when to use it. You will then build a working gateway with Spring Cloud Gateway, step by step: routing, filters, security and response aggregation.
1. What Is an API Gateway?
An API gateway is a server that acts as the single entry point for all client requests in microservices. It sits between the clients (web apps, mobile apps, partner systems) and your backend services. Clients talk only to the gateway; the gateway forwards each request to the right service and returns the response.
An API gateway can also handle cross-cutting concerns such as authentication, authorization, rate limiting, request routing, and logging, reducing the need for each service to implement these features independently.
2. How a Request Flows Through the Gateway
Every clients request passes through the same steps inside an API gateway:
- Receive the request from the client, for example GET /api/orders/42.
- Authenticate the caller by checking their token.
- Apply policies such as rate limits.
- Route: find the matching rule and pick the target service, here the order service.
- Transform the request if needed, for example add headers or rewrite the path.
- Forward the request to a healthy instance of the service, with a timeout.
- Return the response to the client, possibly transformed, and record logs and metrics.
3. Core Responsibilities of an API Gateway
An API Gateway handles the following important responsibilities in a microservices architecture.
| # | Responsibility | What it means |
|---|---|---|
| 1 | Request routing | Sends each request to the correct service based on the URL path, HTTP method or headers |
| 2 | API composition and aggregation | Combines data from several services into one response, so the client makes one call instead of many |
| 3 | Authentication and authorization | Checks API keys, JSON Web Tokens (JWT) or other credentials before requests reach the services |
| 4 | Rate limiting and throttling | Protects services from being overwhelmed by too many requests from one client |
| 5 | Caching | Stores frequent responses to reduce load and improve response times |
| 6 | Load balancing | Spreads requests across several instances of a service |
| 7 | Logging and monitoring | Records every request and collects metrics, giving one place to watch API health |
| 8 | Transformation and protocol translation | Converts between formats and protocols, for example REST for clients and gRPC inside |
Other common jobs include CORS handling, SSL/TLS termination (handling HTTPS so internal services do not have to), request validation and adding correlation IDs for tracing.
The key idea: these are cross-cutting concerns that every service would otherwise implement separately. Moving them to the gateway keeps each service focused on its business logic.
4. Example: The E-Commerce Application
Our example shop has four microservices:
- User service: manages user accounts and profiles.
- Product service: handles product information and details.
- Order service: processes orders and payments.
- Inventory service: tracks product stock levels.
The mobile app needs to show a customer’s order history, including product names and current stock.
Scenario 1: without an API gateway
- Call the user service to check the login.
- Call the order service for the list of orders.
- Call the product service once for each product in those orders.
- Call the inventory service for stock levels.
- Combine all the data inside the app.
Result: many direct calls over a slow mobile network, poor performance and complicated client code that breaks whenever a service changes.
Scenario 2: with an API gateway
The app makes one request: GET /my/order-history. Behind the scenes:
- The mobile app sends the request with its authentication token.
- The gateway validates the token.
- The gateway calls the order, product and inventory services over the fast internal network, in parallel where possible.
- It combines the responses into a single JSON object.
- It returns that one response to the app.
Result: faster screens, simpler client code and a better user experience. The services can change internally without the app noticing.
5. Building a Gateway with Spring Cloud Gateway
Spring Cloud Gateway is the gateway from the Spring family. It is built on Spring Boot, so it is a natural choice for Java teams. A gateway is simply a separate Spring Boot application.
5.1 Dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
Also import the Spring Cloud BOM (spring-cloud-dependencies) in <dependencyManagement> so that versions match your Spring Boot version.
5.2 Routes in configuration
A route has three parts: an ID, a predicate that decides which requests match, and a URI to forward them to. Filters can change the request or response on the way.
server:
port: 8080
spring:
application:
name: shop-gateway
cloud:
gateway:
server:
webflux:
routes:
- id: product-service
uri: http://localhost:8081
predicates:
- Path=/api/products/**
filters:
- StripPrefix=1 # /api/products/7 -> /products/7
- id: order-service
uri: http://localhost:8082
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- id: user-service
uri: http://localhost:8083
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
Clients now call only port 8080. A request to http://localhost:8080/api/products/7 is forwarded to the product service as /products/7.
| Common predicate | Matches when |
|---|---|
Path=/api/orders/** | The URL path matches the pattern |
Method=GET,POST | The HTTP method is one of those listed |
Header=X-Api-Version, 2 | A header has a given value |
Host=**.shop.example.com | The host name matches |
Query=category | A query parameter is present |
Weight=orders, 90 | Used to send a share of traffic, for example 90%, to one version |
Using service discovery: In production, services usually register with a discovery server such as Eureka, or run in Kubernetes. Write uri: lb://product-service instead of a fixed address, and the gateway load-balances across all running instances. Version note: Spring Cloud 2025.0 renamed the starter to spring-cloud-starter-gateway-server-webflux and moved route properties under spring.cloud.gateway.server.webflux. Older versions use spring-cloud-starter-gateway and spring.cloud.gateway.routes.
6. Defining Routes in Java
The same routes can be written in Java, which is handy when you need logic or want compile-time checking:
package com.example.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@Configuration
public class GatewayRoutesConfig {
@Bean
public RouteLocator shopRoutes(RouteLocatorBuilder builder) {
return builder.routes()
// Anyone may read products
.route("product-service", r -> r
.path("/api/products/**")
.and().method(HttpMethod.GET)
.filters(f -> f
.stripPrefix(1)
.addResponseHeader("Cache-Control", "max-age=60"))
.uri("lb://product-service"))
// Orders, with a fallback if the service is down
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.stripPrefix(1)
.circuitBreaker(cb -> cb
.setName("orders")
.setFallbackUri("forward:/fallback/orders")))
.uri("lb://order-service"))
// Send 10% of user traffic to the new version (canary release)
.route("user-service-v2", r -> r
.path("/api/users/**")
.and().weight("users", 10)
.filters(f -> f.stripPrefix(1))
.uri("lb://user-service-v2"))
.route("user-service-v1", r -> r
.path("/api/users/**")
.and().weight("users", 90)
.filters(f -> f.stripPrefix(1))
.uri("lb://user-service"))
.build();
}
}
The weighted routes show a common gateway trick: a canary release. A new version receives a small share of real traffic first, and you increase it once you are confident it works.
The order route’s circuitBreaker filter needs the spring-cloud-starter-circuitbreaker-reactor-resilience4j dependency, and the gateway must handle /fallback/orders, for example with a small controller that returns HTTP 503 and a friendly message. See the Circuit Breaker Pattern guide for details.
7. Filters: Changing Requests and Responses
7.1 Built-in filters
| Filter | What it does |
|---|---|
StripPrefix=1 | Removes the first part of the path, such as /api |
RewritePath=/api/(?<rest>.*), /${rest} | Rewrites the path with a regular expression |
AddRequestHeader=X-Source, gateway | Adds a header before forwarding |
AddResponseHeader=X-Served-By, shop-gateway | Adds a header to the response |
Retry=3 | Retries failed requests, best used for safe GET calls |
RequestRateLimiter | Limits requests per client (see the Rate Limiting Pattern) |
CircuitBreaker | Stops calling a failing service and uses a fallback (see the Circuit Breaker Pattern) |
7.2 A custom global filter: correlation IDs
A global filter runs for every route. This one gives each request a correlation ID, so the same request can be followed through the logs of every service:
package com.example.gateway.filter;
import java.util.UUID;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CorrelationIdFilter implements GlobalFilter, Ordered {
private static final String HEADER = "X-Correlation-Id";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String incoming = exchange.getRequest().getHeaders().getFirst(HEADER);
String correlationId = (incoming != null) ? incoming : UUID.randomUUID().toString();
// Pass the ID on to the backend service...
ServerHttpRequest request = exchange.getRequest().mutate()
.header(HEADER, correlationId)
.build();
// ...and return it to the client, which helps with support requests
exchange.getResponse().getHeaders().add(HEADER, correlationId);
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // run before other filters
}
}
Spring Cloud Gateway (WebFlux edition) is built on Spring WebFlux, so filters return a Mono. Avoid blocking calls, such as plain JDBC, inside filters, because they would stall the gateway’s small pool of threads.
8. Securing APIs at the Gateway
Checking tokens at the gateway means unauthenticated requests are rejected before they reach any service. Add Spring Security’s resource server support:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://login.example.com/realms/shop
package com.example.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchange -> exchange
// Product browsing is public
.pathMatchers(HttpMethod.GET, "/api/products/**").permitAll()
.pathMatchers("/actuator/health").permitAll()
// Everything else needs a valid token
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
// Stateless token API: no cookies, so CSRF protection is not needed
.csrf(ServerHttpSecurity.CsrfSpec::disable);
return http.build();
}
}
The gateway forwards the Authorization header to the services, so they can still read the user’s identity and make their own detailed decisions, such as “customers may only see their own orders”.
9. Aggregating Responses from Multiple Services
Routing forwards one request to one service. However, when a client screen requires data from multiple services, the API Gateway can call those services in parallel and combine their responses into a single result.
package com.example.gateway.aggregation;
import java.util.List;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@RestController
public class DashboardController {
private final WebClient webClient;
public DashboardController(WebClient.Builder builder) {
this.webClient = builder.build();
}
@GetMapping("/my/dashboard")
public Mono<Dashboard> dashboard(@AuthenticationPrincipal Jwt jwt) {
String userId = jwt.getSubject();
Mono<UserProfile> profile = webClient.get()
.uri("http://localhost:8083/users/{id}", userId)
.headers(h -> h.setBearerAuth(jwt.getTokenValue()))
.retrieve()
.bodyToMono(UserProfile.class);
Mono<List<OrderSummary>> recentOrders = webClient.get()
.uri("http://localhost:8082/orders?customerId={id}&limit=5", userId)
.headers(h -> h.setBearerAuth(jwt.getTokenValue()))
.retrieve()
.bodyToFlux(OrderSummary.class)
.collectList();
// Both calls run at the same time; the result is ready when both finish
return Mono.zip(profile, recentOrders, Dashboard::new);
}
public record UserProfile(String id, String name, String email) {
}
public record OrderSummary(Long id, String status, double total) {
}
public record Dashboard(UserProfile profile, List<OrderSummary> recentOrders) {
}
}
Keep aggregation simple: combining and reshaping data is fine, but real business rules belong in the services. If aggregation logic grows large, move it into a dedicated service or a backend for frontend.
10. API Gateway vs Load Balancer and Reverse Proxy
| Tool | Main job | Works at | Typical position |
|---|---|---|---|
| Load balancer | Spreads traffic across server instances | Network or HTTP level | In front of the gateway or of each service |
| Reverse proxy | Forwards requests, handles HTTPS, serves static files | HTTP level | At the edge of the system |
| API gateway | Routing plus API concerns: security, rate limits, aggregation, transformation | API level | Between external clients and services (north–south traffic) |
These tools work together rather than replace each other. A typical setup is: cloud load balancer → API gateway → services.
11. Popular API Gateway Solutions
| Solution | Type | Highlights |
|---|---|---|
| Spring Cloud Gateway | Open source, Java | Built on Spring Boot; routes in YAML or Java; natural fit for Spring teams |
| Kong | Open source and commercial | High-performance gateway built on NGINX, with a large plugin ecosystem |
| AWS API Gateway | Fully managed | Serverless, integrates with Lambda, IAM and other AWS services |
| Azure API Management | Fully managed | Complete API management from Microsoft, including a developer portal |
| Google Cloud API Gateway | Fully managed | Serverless gateway for Google Cloud back ends |
| Apigee | Managed platform | Full API management from Google Cloud: analytics, monetization, policies |
| Traefik | Open source | Modern reverse proxy and load balancer with automatic Kubernetes and Docker discovery |
| NGINX | Open source and commercial | Fast, widely used reverse proxy that can act as a simple gateway |
| Envoy | Open source | High-performance proxy; the base for many gateways and service meshes |
How to choose: a Java and Spring team that wants full control often picks Spring Cloud Gateway. Teams fully on one cloud often pick that cloud’s managed gateway to avoid running servers. Organizations that publish APIs to many external partners often need a full API management platform such as Apigee, Kong or Azure API Management.
12. Benefits and Drawbacks of an API Gateway
12.1 Benefits
- Clients use one address and make fewer calls.
- Security, rate limiting and logging live in one place.
- Internal services are hidden and can change freely.
- Easier versioning, canary releases and migrations.
- Responses can be tailored and aggregated for clients.
12.2 Drawbacks
- Another component to build, run and monitor.
- Can become a single point of failure if not made highly available.
- Adds a network hop, and so a little latency.
- Risk of becoming a bottleneck if too much logic is added.
- One shared gateway can slow teams down if every change needs the gateway team.
Avoid the single point of failure by running several gateway instances behind a load balancer, across availability zones. Keep the extra latency small by keeping filters light and avoiding blocking calls.
13. Best Practices
- Keep the gateway thin. Routing and cross-cutting concerns only; business logic belongs in services.
- Run several instances behind a load balancer so the gateway is never a single point of failure.
- Set timeouts on every route and use circuit breakers with fallbacks for important services.
- Validate tokens at the gateway, and still authorize in the services (defense in depth).
- Apply rate limits per client or per user, and return
429with a clear message. - Add correlation IDs and central logging, so a request can be followed across services.
- Use service discovery (
lb://) instead of hard-coded addresses. - Version your APIs, for example
/api/v1/..., and use the gateway to route versions. - Keep internal services private: only the gateway should be reachable from the internet.
- Monitor the gateway closely: latency, error rates and rate-limit rejections per route.
14. Common Mistakes to Avoid
| Mistake | What goes wrong | Better approach |
|---|---|---|
| Putting business logic in the gateway | The gateway becomes a new monolith that every team must change | Keep business rules in services; use BFFs for client-specific shaping |
| Running only one instance | One crash takes the whole system offline | Run several instances behind a load balancer |
| No timeouts | One slow service exhausts the gateway and every route slows down | Set connect and response timeouts, plus circuit breakers |
| Trusting the gateway alone for security | Internal callers or a route mistake bypass all checks | Also validate and authorize in each service |
| Blocking calls in reactive filters | The gateway’s few threads stall and throughput collapses | Use non-blocking clients such as WebClient |
| Retrying non-idempotent requests | A retried POST can create duplicate orders or payments | Retry only safe methods, or use idempotency keys |
| Services still reachable from outside | Attackers skip the gateway entirely | Expose only the gateway; keep services on a private network |
15. Summary
The API gateway is the front door of a microservices architecture. It gives clients a single entry point, routes each request to the right service, and handles cross-cutting concerns such as authentication, rate limiting, caching, logging and response aggregation in one place. With Spring Cloud Gateway you can define routes in YAML or Java, add filters, secure APIs with JWT, limit traffic with Redis, and protect the system with timeouts and circuit breakers. Keep the gateway thin and highly available, combine it with backends for frontends when clients differ a lot, and your microservices stay independent while the outside world sees one clean, secure and scalable API. Continue with the Circuit Breaker Pattern to learn more about keeping services resilient.
API Gateway Pattern: Interview Questions
Common interview questions on API Gateway Pattern, from basic to scenario-based, with short answers.
Basic questions
Q. Do I always need an API gateway for microservices?
Answer: No. A gateway is most useful when you have several services, external clients such as mobile apps or partners, and shared concerns such as security and rate limiting.
With only two or three services used by one internal web app, a simple reverse proxy or even direct calls may be enough. Add a gateway when the problems it solves actually appear, because it is one more component to run.
Q. What is the difference between an API gateway and a load balancer?
Answer: A load balancer spreads traffic across identical server instances. It does not understand your API.
An API gateway understands requests at the API level: it routes /orders and /products to different services, checks tokens, applies rate limits and can combine responses. Gateways often use load balancing internally, and a load balancer often sits in front of the gateway instances.
Q. What is the difference between an API gateway and a service mesh?
Answer:
- An API gateway handles north–south traffic: requests coming from outside clients into the system.
- A service mesh, such as Istio or Linkerd, handles east–west traffic: calls between services inside the system, adding encryption (mTLS), retries and metrics through sidecar proxies.
Large systems often use both: a gateway at the edge and a mesh inside.
Q. What does "thin gateway" mean?
Answer: A thin gateway does only infrastructure work: routing, security checks, rate limiting, logging and simple transformations. It contains no business rules, such as discount calculations or order validation.
A thick gateway full of business logic becomes a new monolith: every feature needs a gateway change, and one bug can break all services. Keeping it thin preserves the independence that microservices are meant to give you.
Q. Is an API gateway the same as API management?
Answer: Not quite. An API gateway is the runtime component that handles traffic. API management is a broader platform that usually includes a gateway, plus:
- A developer portal with documentation and sign-up for API keys.
- Analytics on who uses which API and how much.
- Monetization, plans and quotas for paying customers.
- Life-cycle management for designing, publishing and retiring APIs.
Apigee, Azure API Management and Kong’s enterprise offering are API management platforms; Spring Cloud Gateway is a gateway.
Intermediate questions
Q. What is the difference between Spring Cloud Gateway and Netflix Zuul?
Answer: Zuul 1 was Netflix’s gateway and was popular with early Spring Cloud. It used a blocking, thread-per-request model and is no longer part of Spring Cloud.
Spring Cloud Gateway is its replacement in the Spring world. It is actively maintained, built on modern Spring, and comes in a reactive (WebFlux) edition for high concurrency and a servlet (Web MVC) edition for teams that prefer the classic blocking style. New projects should use Spring Cloud Gateway.
Q. Can I use Spring Cloud Gateway without WebFlux?
Answer: Yes. Besides the reactive edition used in this guide, there is a Web MVC edition built on the classic servlet stack:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webmvc</artifactId>
</dependency>
It is a good fit if your team does not use reactive programming. Its routes are configured in a similar way, but its filters and Java APIs differ, so pick one edition per gateway.
Q. How does the gateway find services that scale up and down?
Answer: Through service discovery. Services register themselves, and the gateway looks them up by name:
- With Eureka or Consul, add the discovery client and use
uri: lb://order-service. - On Kubernetes, route to the service’s DNS name, such as
http://order-service:8080; Kubernetes balances across pods.
Either way, you never hard-code individual server addresses.
Q. How do I version APIs with a gateway?
Answer: The gateway is a natural place to route API versions to different services or deployments:
routes:
- id: orders-v1
uri: lb://order-service
predicates:
- Path=/api/v1/orders/**
- id: orders-v2
uri: lb://order-service-v2
predicates:
- Path=/api/v2/orders/**
Old clients keep using /api/v1 while new clients move to /api/v2. Header-based versioning works the same way with a Header predicate.
Q. Should I cache responses at the gateway?
Answer: Caching at the gateway works well for public, rarely changing data, such as product catalogs or configuration. It reduces load on services and speeds up responses.
Be careful with:
- Personal data: never serve one user’s cached response to another.
- Freshness: set short expiry times for data that changes, such as stock levels.
Often the simplest approach is to send proper Cache-Control headers so browsers and CDNs cache for you.
Q. How do I handle CORS at the gateway?
Answer: Configure CORS once at the gateway instead of in every service:
spring:
cloud:
gateway:
server:
webflux:
globalcors:
cors-configurations:
'[/api/**]':
allowed-origins: "https://shop.example.com"
allowed-methods: GET,POST,PUT,DELETE
allowed-headers: Authorization,Content-Type
Then make sure the services do not add their own CORS headers as well, because duplicate headers make browsers reject the response.
Q. Can the gateway retry failed requests?
Answer: Yes, with the Retry filter, but use it carefully:
filters:
- name: Retry
args:
retries: 3
methods: GET
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
Only retry safe, idempotent requests such as GET. Retrying a POST that creates an order could create it twice. Also keep retries few, or they can multiply load on a service that is already struggling.
Q. How do I monitor an API gateway?
Answer: Spring Cloud Gateway works with Spring Boot Actuator and Micrometer. Watch at least:
- Request rate, error rate and latency per route.
- Rate-limit rejections (
429responses). - Circuit breaker states: which circuits are open.
- Gateway instance health: CPU, memory and connections.
Combine metrics with correlation IDs and distributed tracing to follow a slow request through every service.
Q. Can one gateway serve both REST and WebSocket traffic?
Answer: Yes. Spring Cloud Gateway can route WebSocket connections with ws:// or wss:// URIs, or lb:ws:// with service discovery:
- id: notifications
uri: lb:ws://notification-service
predicates:
- Path=/ws/notifications/**
Remember that WebSocket connections stay open for a long time, so plan gateway capacity and load balancer timeouts accordingly.
Q. How do I test routes in Spring Cloud Gateway?
Answer: Start the gateway in a test and point routes at a mock server, then call it with WebTestClient:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class GatewayRoutesTest {
@Autowired
private WebTestClient client;
@Test
void productRequestsAreRoutedAndPrefixIsStripped() {
client.get().uri("/api/products/7")
.exchange()
.expectStatus().isOk();
}
@Test
void ordersRequireAToken() {
client.get().uri("/api/orders/1")
.exchange()
.expectStatus().isUnauthorized();
}
}
Tools such as WireMock can stand in for the backend services, so you can check paths, headers and fallbacks without starting real services.
Tricky and scenario questions
Q. Isn't the API gateway a single point of failure?
Answer: It can be, if you run only one instance. In practice, you avoid this by:
- Running several gateway instances behind a load balancer.
- Spreading them across availability zones.
- Keeping the gateway stateless, so any instance can handle any request.
- Using health checks so broken instances are removed automatically.
Managed cloud gateways handle this for you.
Q. Does an API gateway make requests slower?
Answer: It adds one extra network hop, usually a few milliseconds inside a data center. For most applications this is small compared with the time saved elsewhere:
- Clients make one call instead of many over slow internet connections.
- The gateway can cache frequent responses.
- Calls between the gateway and services use the fast internal network.
Keep filters light and non-blocking, and the overhead stays negligible.
Q. Should the gateway or the services check user permissions?
Answer: Both, at different levels:
- The gateway checks that the request carries a valid token and may apply broad rules, such as “only admins can call
/api/admin/**”. - Each service applies detailed rules that need its own data, such as “customers may only see their own orders”.
This layered approach, called defense in depth, keeps you safe even if a route is misconfigured or an internal system calls a service directly.
Q. Where should request validation happen?
Answer: Split it sensibly:
- The gateway can reject obviously bad requests early: missing tokens, oversized bodies, unknown paths or wrong content types.
- The service validates the business content: required fields, valid quantities, existing product IDs.
Do not move detailed business validation into the gateway, or every rule change will need a gateway release.
Q. How many gateways should a system have?
Answer: Start with one logical gateway, run as several instances. Consider more when:
- Clients differ a lot, such as mobile, web and partners: use backends for frontends.
- Internal and external APIs need very different security: use separate external and internal gateways.
- One gateway team becomes a bottleneck for many product teams.
Avoid creating a gateway per service; that only adds hops without benefits.