Spring Boot
API Gateway
An API Gateway is the single entry point for all client requests in a microservices architecture. It sits between clients and backend services, handling cross-cutting concerns such as routing, authentication, rate limiting, SSL termination, and request/response transformation. Clients talk to one stable URL; the gateway fans out to whichever internal services are needed.
Why an API Gateway Is Needed
Without a gateway, clients must know the address of every microservice, handle partial failures across multiple calls, and repeat cross-cutting logic (auth, logging, CORS) in every service. The gateway centralises all of that. It also decouples clients from the internal service topology β services can be split, renamed, or relocated without any client change.
Java
// ββ Without API Gateway: βββββββββββββββββββββββββββββββββββββββββββββ
//
// Mobile App
// βββ GET http://user-service:8081/api/users/1
// βββ GET http://order-service:8082/api/orders?userId=1
// βββ GET http://payment-service:8083/api/payments?userId=1
//
// Problems:
// β’ Client must know 3 different host:port combinations
// β’ Each service must implement auth, CORS, rate limiting independently
// β’ Any service rename/move requires a client update
// β’ Mobile app makes 3 round trips β slow on poor connections
// β’ No single place to enforce security policies
// ββ With API Gateway: βββββββββββββββββββββββββββββββββββββββββββββββββ
//
// Mobile App
// βββ All requests β http://api.example.com (gateway)
// β
// βββ /api/users/** βββΆ user-service:8081
// βββ /api/orders/** βββΆ order-service:8082
// βββ /api/payments/** βββΆ payment-service:8083
//
// Benefits:
// + Client knows exactly ONE address
// + Auth/CORS/rate limiting enforced ONCE at the gateway
// + Internal topology hidden β move services without client changes
// + Gateway can aggregate multiple service calls into one response
// + SSL terminated at the gateway β internal traffic can be plain HTTP
// ββ What a gateway handles on every request: βββββββββββββββββββββββββ
//
// Inbound request
// β
// βΌ
// [1] SSL Termination β decrypt HTTPS, forward HTTP internally
// [2] Authentication β validate JWT / API key
// [3] Rate Limiting β reject if client exceeds quota
// [4] Request Logging β log method, path, client IP, latency
// [5] Routing β which service handles this path?
// [6] Load Balancing β which instance of that service?
// [7] Request Transform β add/remove headers, rewrite paths
// β
// βΌ
// Downstream Service
// β
// βΌ
// [8] Response Transform β add CORS headers, strip internal headers
// [9] Circuit Breaking β return fallback if service is down
// β
// βΌ
// ClientGateway Responsibilities
A gateway is responsible for a well-defined set of cross-cutting concerns. Each concern that the gateway handles is one less thing every downstream service needs to implement independently. The most critical are routing, authentication, and rate limiting.
Java
// ββ 1. ROUTING β path-based and header-based: ββββββββββββββββββββββββ
//
// /api/users/** β user-service
// /api/orders/** β order-service
// /api/v2/** β new version of services (canary routing)
// Header: X-Beta: true β beta-service (header-based routing)
// ββ 2. AUTHENTICATION β centralised JWT validation: βββββββββββββββββββ
//
// Gateway validates the JWT on every request.
// If valid β strip the token, forward user identity in a header.
// If invalid β return 401 immediately; downstream never sees the request.
//
// Downstream services trust the gateway:
// X-User-Id: 42
// X-User-Role: ADMIN
// No need for each service to re-validate the JWT.
// ββ 3. RATE LIMITING β protect services from overload: ββββββββββββββββ
//
// Per-client limits (by IP or API key):
// Free tier: 100 requests / minute
// Pro tier: 1,000 requests / minute
// Internal: unlimited
//
// Exceeded β 429 Too Many Requests (before the request reaches services)
// ββ 4. REQUEST / RESPONSE TRANSFORMATION: ββββββββββββββββββββββββββββ
//
// Add upstream headers:
// X-Request-Id: uuid (for distributed tracing)
// X-Gateway-Source: gateway (so services know request came via gateway)
// X-Real-IP: <client IP>
//
// Remove sensitive response headers before returning to client:
// Remove: X-Powered-By, Server, X-Internal-Service-Version
// ββ 5. AGGREGATION (Backend for Frontend pattern): ββββββββββββββββββββ
//
// Mobile client needs: user profile + recent orders + payment status
// Instead of 3 client round trips, the gateway (or a BFF service) calls
// all three services and returns a single merged response.
//
// Client: GET /api/dashboard/42
// Gateway calls:
// β user-service/api/users/42
// β order-service/api/orders?userId=42&limit=5
// β payment-service/api/payments?userId=42&status=PENDING
// Returns: { user: {...}, orders: [...], pendingPayments: [...] }
// ββ 6. CIRCUIT BREAKING β fallback when a service is down: ββββββββββββ
//
// payment-service is returning 503:
// Without circuit breaker β gateway waits, threads pile up, gateway hangs
// With circuit breaker β gateway returns cached/default response fast
// Fallback response: { "paymentStatus": "unavailable" }API Gateway vs Load Balancer vs Reverse Proxy
These three components are often confused. A reverse proxy (nginx, HAProxy) forwards traffic and handles SSL. A load balancer distributes traffic across identical instances of the same service. An API gateway does both of those β and adds application-layer intelligence such as authentication, routing by path, request transformation, and circuit breaking.
Java
// ββ Comparison: βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
//
// ββββββββββββββββββββββ¬ββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ
// β Feature β Reverse Proxy β Load Balancerβ API Gateway β
// ββββββββββββββββββββββΌββββββββββββββββΌβββββββββββββββΌβββββββββββββββ€
// β SSL termination β β β β β β β
// β Forward to backend β β β β β β β
// β Distribute load β basic β β β β β
// β Path-based routing β limited β β β β β
// β Header-based route β β β β β β β
// β Authentication β β β β β β β
// β Rate limiting β β β β β β β
// β Request transform β β β β β β β
// β Circuit breaking β β β β β β β
// β Service discovery β β β β β β β
// ββββββββββββββββββββββ΄ββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ
//
// Real-world stack β these are often layered:
//
// Internet
// β
// Cloud Load Balancer (AWS ALB / GCP LB) β distributes across gateway pods
// β
// API Gateway (Spring Cloud Gateway) β routing, auth, rate limiting
// β
// Microservices β business logic
// ββ Gateway types: βββββββββββββββββββββββββββββββββββββββββββββββββββ
//
// Self-hosted (code in your repo):
// Spring Cloud Gateway β Java / Spring Boot
// Netflix Zuul 2 β Java (legacy, mostly replaced by SCG)
//
// Infrastructure / cloud-managed:
// AWS API Gateway β managed, serverless-friendly
// Kong β plugin-based, Lua / Go
// NGINX API Gateway β high-performance C-based proxy
// Traefik β cloud-native, auto-discovers Docker/K8s servicesBackend for Frontend (BFF) Pattern
A single gateway serving all clients can become bloated with client-specific logic. The Backend for Frontend pattern uses a dedicated gateway per client type β one for the web app, one for mobile, one for third-party partners. Each BFF aggregates and shapes data for its specific client without burdening other clients or downstream services.
Java
// ββ Single gateway problem: βββββββββββββββββββββββββββββββββββββββββββ
//
// Web app β needs full user profile, all order fields, charts data
// Mobile appβ needs minimal profile, 3 most recent orders, no charts
// Partner β needs only order status and tracking number (no user PII)
//
// One gateway trying to serve all three β complex conditional logic,
// over-fetching on mobile, under-fetching for web.
// ββ BFF pattern β one gateway per client: βββββββββββββββββββββββββββββ
//
// βββββββββββββββββββ
// Browser βββββββββββΆβ Web BFF :8080 β
// ββββββββββ¬βββββββββ
// β calls user-service, order-service,
// β analytics-service, returns rich payload
//
// βββββββββββββββββββ
// Mobile App βββββββββΆβ Mobile BFF :8081β
// ββββββββββ¬βββββββββ
// β calls same services, returns
// β trimmed/compressed payload
//
// βββββββββββββββββββ
// Partner API ββββββββΆβPartner BFF :8082β
// ββββββββββ¬βββββββββ
// β exposes only public-safe fields,
// β enforces partner-specific rate limits
//
// All three BFFs call the same downstream microservices.
// Each shapes the response for its specific consumer.
// ββ Mobile BFF example (returns trimmed order list): βββββββββββββββββ
@RestController
@RequiredArgsConstructor
@RequestMapping("/mobile/api")
public class MobileBffController {
private final UserClient userClient;
private final OrderClient orderClient;
@GetMapping("/dashboard/{userId}")
public MobileDashboardResponse getDashboard(@PathVariable Long userId) {
UserResponse user = userClient.findById(userId);
List<OrderResponse> orders = orderClient.findRecent(userId, 3);
// Shape response specifically for mobile β minimal payload:
return MobileDashboardResponse.builder()
.userName(user.getFirstName()) // not full profile
.avatarUrl(user.getAvatarUrl())
.recentOrders(orders.stream()
.map(o -> new OrderSummary(o.getId(), o.getStatus()))
.toList()) // status only, no line items
.build();
}
}