Definition
- API Gateway pattern – a structural microservices pattern where a dedicated service acts as the single reverse-proxy entry point between external clients and internal services.
- Responsibilities – strictly cross-cutting: routing, protocol translation, authentication, aggregation, rate limiting, caching, observability (logging, tracing), and resilience (circuit breaking, retries).
- Filter chain – each responsibility is implemented as a filter in an ordered chain that the request passes through.
- Boundary rule – the gateway deliberately holds no business logic so it remains a thin, generic edge layer.
- BFF variant – Backend-for-Frontend runs a tailored gateway per client type (mobile, web, partner).
The API Gateway pattern is like having one official reception and security desk for the entire National Bank of India head office. Every visitor, whether Rajesh coming for a loan or Priya coming about her current account, enters through that one desk. The desk checks IDs, logs who came in and when, limits crowding, and sends each person to the right department. The desk never actually approves a loan or moves money; that is the department's job. It only manages access and direction.
Understanding the Concepts
What is the routing responsibility?
Routing is the foundation. The gateway inspects the incoming request and forwards it to the matching service. This hides the internal topology so services can be split, renamed, or scaled without changing clients.
package com.bank.gateway;
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;
@Configuration
public class RoutingConfig {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("accounts", r -> r
.path("/api/v1/accounts/**")
.uri("lb://account-service"))
.route("payments", r -> r
.path("/api/v1/payments/**")
.uri("lb://payment-service"))
.route("loans", r -> r
.path("/api/v1/loans/**")
.uri("lb://loan-service"))
.build();
}
}
// /api/v1/accounts/SAV2001 → account-service // /api/v1/payments/transfer → payment-service // /api/v1/loans/apply → loan-service // Client knows one address; internal topology is hidden
What is protocol translation?
External clients speak REST over HTTP/JSON. Internal services may speak gRPC over HTTP/2 with protobuf. The gateway translates between them so clients stay simple.
// External request (REST/JSON):
// POST /api/v1/accounts/SAV2001/debit
// Body: {"amount": 50000, "currency": "INR"}
// Gateway translates to internal gRPC call:
// accountStub.debit(DebitRequest.newBuilder()
// .setAccountId("SAV2001")
// .setAmount("50000")
// .setCurrency("INR")
// .build());
// Client never knows the internal protocol is gRPC
// Internal service can switch from REST to gRPC without client changes
What is the authentication responsibility?
The gateway verifies the caller's token once at the edge, confirms the signature and expiry, and passes a trusted identity context to services.
package com.bank.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {
private final JwtDecoder jwtDecoder;
public AuthenticationFilter(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authHeader = exchange.getRequest().getHeaders()
.getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
try {
var claims = jwtDecoder.decode(authHeader.substring(7));
exchange.getRequest().mutate()
.header("X-User-Id", claims.getSubject());
return chain.filter(exchange);
} catch (Exception e) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
@Override
public int getOrder() {
return -100; // Run early in the filter chain
}
}
// Request with valid JWT → gateway verifies → adds X-User-Id header // Services trust X-User-Id without re-validating the token // Invalid or expired JWT → 401 Unauthorized, service never sees it
What is the aggregation responsibility?
For a client view that needs data from several services, the gateway calls them and merges the results into one response.
// Mobile dashboard needs: account + cards + recent payments
// Without gateway: 3 calls from mobile (3 round trips, 3 auth checks)
// With gateway: 1 call, gateway does 3 parallel calls internally
// Mono.zip(accountMono, cardsMono, paymentsMono)
// .map(tuple -> Map.of(
// "account", tuple.getT1(),
// "cards", tuple.getT2(),
// "payments", tuple.getT3()));
What is the rate limiting responsibility?
Rate limiting protects services from overload. The gateway counts requests per client and rejects excess traffic with HTTP 429.
# Spring Cloud Gateway rate limiter
spring:
cloud:
gateway:
routes:
- id: payments
uri: lb://payment-service
predicates:
- Path=/api/v1/payments/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 50
redis-rate-limiter.burstCapacity: 100
// Rajesh's app sends 60 requests/second:
// First 50: processed (replenish rate)
// Next 50: burst capacity allows up to 100
// Beyond 100: HTTP 429 Too Many Requests
What is the resilience responsibility?
Timeouts, retries, and circuit breakers wrap each downstream call so a failing service fails fast with a graceful fallback.
// Route with circuit breaker:
// .filters(f -> f.circuitBreaker(cb -> cb
// .setName("accountCB")
// .setFallbackUri("forward:/fallback/accounts")))
// Account Service healthy → request forwarded normally
// Account Service down → circuit OPEN → fallback: {"status": "unavailable"}
// The gateway never hangs; it fails fast
What is the observability responsibility?
The gateway assigns a correlation ID (traceId), logs every request, and emits metrics so one request can be followed across the whole system.
// With Micrometer Tracing:
// Gateway: traceId=abc123 - Received POST /api/v1/payments/transfer
// Payment: traceId=abc123 - Processing transfer TXN-456
// Account: traceId=abc123 - Debiting SAV2001
// All logs searchable by traceId=abc123
What must the gateway NOT do?
The gateway must not contain business logic, own data, or become an orchestration dumping ground. If it does, it turns into a distributed monolith.
// WRONG: business logic in the gateway
public Mono<Object> transfer(TransferRequest req) {
// Fee calculation, balance check, compliance rules
// in the gateway = distributed monolith
}
// CORRECT: gateway only routes
// /api/v1/transfers → lb://payment-service
// Payment Service handles all business logic
// WRONG: gateway owns a database
// Gateway queries its own cache_db for user preferences
// This couples the gateway to a data store
// CORRECT: gateway is stateless (except ephemeral caches)
What should a senior engineer know?
A senior engineer thinks of the gateway as an ordered chain of filters: tracing, authentication, rate limiting, routing. Each filter can reject the request early. The gateway is horizontally scaled behind a load balancer. For different clients, the BFF variant runs a tailored gateway per client type.
// Filter chain order: // 1. Tracing filter: stamp traceId // 2. Auth filter: validate JWT → reject if invalid // 3. Rate limit filter: check budget → reject if exceeded // 4. Routing filter: forward to backend service // Early rejection: unauthenticated request never reaches the service
Visual Overview
API Gateway Pattern: Filter Chain
──────────────────────────────────
Client Request
│
▼
┌──────────────┐
│ Tracing │ → stamp traceId
└──────┬───────┘
▼
┌──────────────┐
│ Auth Filter │ → validate JWT → reject 401 if invalid
└──────┬───────┘
▼
┌──────────────┐
│ Rate Limiter │ → check token bucket → reject 429 if exceeded
└──────┬───────┘
▼
┌──────────────┐
│ Router │ → match path predicate → forward to service
└──────┬───────┘
▼
┌──────────────┐
│ Circuit Brkr │ → if service down → fallback
└──────┬───────┘
▼
Backend Service
Backend-for-Frontend
────────────────────
Mobile App ──▶ Mobile BFF Gateway (lean payload)
Web App ──▶ Web BFF Gateway (rich payload)
Partner API ──▶ Partner Gateway (strict rate limits)
│
┌──────┼──────┐
Account Payment Card
Key Takeaway
The API Gateway pattern is a structural microservices pattern where one dedicated edge service becomes the single entry point between external clients and all the internal services. Its responsibilities are strictly cross-cutting: routing, protocol translation, authentication, aggregation, rate limiting, caching, observability, and resilience. The defining rule is that every one of those is a cross-cutting concern, and the gateway deliberately holds no business logic at all.
At National Bank of India, a request flows through an ordered chain of filters. The tracing filter stamps a correlation ID. The authentication filter verifies the JWT once at the edge. The rate-limit filter protects services from overload. The routing filter forwards the request to the right service. If any filter rejects the request, the chain stops immediately, so an unauthenticated call to the Payment Service is turned away at the edge and the service never sees it.
The gateway gives two big benefits: it decouples clients from the internal layout, because the app talks to one stable address while services behind it can change, and it removes duplicated edge logic, because authentication and rate limiting live in one place instead of being copied into every service. The discipline is to keep the gateway thin. It must not own business logic, data, or orchestration, because the moment it does, it becomes a distributed monolith.
Remember one sentence: the API Gateway pattern puts one thin edge service in front of all your services to own every cross-cutting concern, and nothing that is business logic.
---