API Gateway¶
What is an API Gateway?¶
An API Gateway is a server that acts as a single entry point for a set of microservices, handling cross-cutting concerns like authentication, rate limiting, routing, and protocol translation.
Popular Options¶
| Gateway | Type | Best For |
|---|---|---|
| AWS API Gateway | Managed | AWS ecosystem, serverless |
| Kong | Open Source/Enterprise | Kubernetes, plugins |
| NGINX | Open Source | High performance |
| Envoy | Open Source | Service mesh, Kubernetes |
| Apigee | Enterprise (Google) | API management, analytics |
| Azure API Management | Managed | Azure ecosystem |
| Spring Cloud Gateway | Framework | Spring Boot apps |
| Traefik | Open Source | Docker, Kubernetes |
| Ambassador | Open Source | Kubernetes-native |
Core Features¶
Architecture Patterns¶
Single Gateway¶
Backend for Frontend (BFF)¶
Gateway with Mesh¶
Common Use Cases¶
1. Authentication & Authorization¶
# Kong configuration example
plugins:
- name: jwt
config:
secret_is_base64: false
claims_to_verify:
- exp
- nbf
- name: acl
config:
whitelist:
- admin
- user
// Spring Cloud Gateway
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("auth_route", r -> r
.path("/api/**")
.filters(f -> f
.filter(new JwtAuthenticationFilter())
.filter(new RoleAuthorizationFilter("ADMIN")))
.uri("lb://backend-service"))
.build();
}
2. Rate Limiting¶
# Kong rate limiting
plugins:
- name: rate-limiting
config:
minute: 100
hour: 10000
policy: local # or redis for distributed
fault_tolerant: true
// Spring Cloud Gateway with Redis
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(10, 20); // 10 req/sec, burst of 20
}
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rate_limited", r -> r
.path("/api/**")
.filters(f -> f.requestRateLimiter(c -> c
.setRateLimiter(redisRateLimiter())
.setKeyResolver(exchange ->
Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()))))
.uri("lb://backend"))
.build();
}
3. Request Routing¶
# NGINX configuration
upstream user_service {
server user-svc-1:8080;
server user-svc-2:8080;
}
upstream order_service {
server order-svc-1:8080;
server order-svc-2:8080;
}
server {
listen 80;
location /api/users {
proxy_pass http://user_service;
}
location /api/orders {
proxy_pass http://order_service;
}
# Canary routing
location /api/v2/users {
proxy_pass http://user_service_v2;
}
}
# Kong routing
services:
- name: user-service
url: http://user-svc:8080
routes:
- name: users-route
service: user-service
paths:
- /api/users
strip_path: false
4. Request/Response Transformation¶
# Kong request transformer
plugins:
- name: request-transformer
config:
add:
headers:
- X-Request-ID:$(uuid)
- X-Forwarded-Service:user-service
remove:
headers:
- X-Internal-Header
// Spring Cloud Gateway
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("transform", r -> r
.path("/api/**")
.filters(f -> f
.addRequestHeader("X-Request-ID", UUID.randomUUID().toString())
.removeRequestHeader("X-Internal-Header")
.modifyResponseBody(String.class, String.class,
(exchange, body) -> Mono.just(wrapResponse(body))))
.uri("lb://backend"))
.build();
}
5. API Versioning¶
# Path-based versioning
routes:
- name: users-v1
paths: ["/v1/users"]
service: user-service-v1
- name: users-v2
paths: ["/v2/users"]
service: user-service-v2
# Header-based versioning
- name: users-v1
paths: ["/users"]
headers:
API-Version: "1"
service: user-service-v1
- name: users-v2
paths: ["/users"]
headers:
API-Version: "2"
service: user-service-v2
6. Circuit Breaker¶
// Spring Cloud Gateway with Resilience4j
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("circuit_breaker", r -> r
.path("/api/**")
.filters(f -> f
.circuitBreaker(config -> config
.setName("backendCircuitBreaker")
.setFallbackUri("forward:/fallback")))
.uri("lb://backend"))
.build();
}
@RestController
public class FallbackController {
@GetMapping("/fallback")
public ResponseEntity<String> fallback() {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("Service temporarily unavailable");
}
}
# Kong circuit breaker
plugins:
- name: circuit-breaker
config:
threshold: 5
window_size: 60
timeout: 30
half_open_after: 30
7. Caching¶
# Kong caching
plugins:
- name: proxy-cache
config:
response_code:
- 200
request_method:
- GET
- HEAD
content_type:
- application/json
cache_ttl: 300
strategy: memory
# NGINX caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
max_size=100m inactive=60m use_temp_path=off;
server {
location /api/ {
proxy_cache api_cache;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
8. SSL/TLS Termination¶
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.crt;
ssl_certificate_key /etc/ssl/private/api.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://backend; # HTTP to backend
}
}
AWS API Gateway¶
REST API¶
# SAM template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Auth:
DefaultAuthorizer: MyCognitoAuthorizer
Authorizers:
MyCognitoAuthorizer:
UserPoolArn: !GetAtt MyUserPool.Arn
GetUserFunction:
Type: AWS::Serverless::Function
Properties:
Handler: users.get
Runtime: nodejs18.x
Events:
GetUser:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /users/{id}
Method: GET
HTTP API (Faster, Cheaper)¶
Resources:
HttpApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: prod
CorsConfiguration:
AllowOrigins:
- "https://example.com"
AllowMethods:
- GET
- POST
Features Comparison¶
| Feature | REST API | HTTP API |
|---|---|---|
| Latency | Higher | ~60% lower |
| Cost | Higher | ~70% cheaper |
| API Keys | Yes | No |
| Usage Plans | Yes | No |
| Caching | Yes | No |
| Request Validation | Yes | No |
| WAF | Yes | No |
| Private APIs | Yes | Yes |
| Lambda Integration | Yes | Yes |
| JWT Auth | Custom | Native |
Kong Gateway¶
Docker Compose Setup¶
version: '3'
services:
kong-database:
image: postgres:13
environment:
POSTGRES_USER: kong
POSTGRES_DB: kong
POSTGRES_PASSWORD: kong
kong-migration:
image: kong:latest
command: kong migrations bootstrap
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_PASSWORD: kong
depends_on:
- kong-database
kong:
image: kong:latest
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_PASSWORD: kong
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: 0.0.0.0:8001
ports:
- "8000:8000" # Proxy
- "8001:8001" # Admin API
depends_on:
- kong-migration
Declarative Configuration¶
# kong.yml
_format_version: "2.1"
services:
- name: user-service
url: http://user-svc:8080
routes:
- name: users
paths:
- /api/users
strip_path: false
plugins:
- name: rate-limiting
config:
minute: 100
- name: jwt
config:
secret_is_base64: false
- name: order-service
url: http://order-svc:8080
routes:
- name: orders
paths:
- /api/orders
consumers:
- username: mobile-app
plugins:
- name: rate-limiting
config:
minute: 1000
Spring Cloud Gateway¶
Dependencies¶
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Configuration¶
# application.yml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- AddRequestHeader=X-Request-ID, ${random.uuid}
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
- Header=Authorization, Bearer.*
filters:
- StripPrefix=1
- CircuitBreaker=name=orderCircuitBreaker,fallbackUri=forward:/fallback
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "https://example.com"
allowedMethods:
- GET
- POST
- PUT
- DELETE
Custom Filter¶
@Component
public class AuthenticationFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// Check Authorization header
if (!request.getHeaders().containsKey("Authorization")) {
return onError(exchange, "No Authorization header", HttpStatus.UNAUTHORIZED);
}
String authHeader = request.getHeaders().getFirst("Authorization");
if (!authHeader.startsWith("Bearer ")) {
return onError(exchange, "Invalid Authorization header", HttpStatus.UNAUTHORIZED);
}
String token = authHeader.substring(7);
try {
Claims claims = validateToken(token);
// Add user info to headers for downstream services
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-ID", claims.getSubject())
.header("X-User-Roles", claims.get("roles", String.class))
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
} catch (Exception e) {
return onError(exchange, "Invalid token", HttpStatus.UNAUTHORIZED);
}
}
private Mono<Void> onError(ServerWebExchange exchange, String message, HttpStatus status) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(status);
return response.setComplete();
}
@Override
public int getOrder() {
return -100; // Run early in filter chain
}
}
Trade-offs¶
| Pros | Cons |
|---|---|
| Single entry point | Single point of failure |
| Cross-cutting concerns centralized | Additional latency |
| Protocol translation | Added complexity |
| Security at the edge | Can become bottleneck |
| Simplified clients | Another component to manage |
| Traffic management | Potential coupling |
Performance Considerations¶
| Factor | Impact |
|---|---|
| Connection pooling | Critical for backend connections |
| Caching | Reduces backend load |
| Compression | Reduces bandwidth |
| HTTP/2 | Multiplexing, header compression |
| SSL session caching | Reduces handshake overhead |
| Rate limiting | Prevents overload |
| Timeouts | Prevents hanging connections |
When to Use API Gateway¶
Good For: - Microservices architecture - Multi-client applications (web, mobile, IoT) - Third-party API exposure - Legacy modernization - Cross-cutting concerns
Consider Alternatives When: - Simple monolith (use reverse proxy) - Service mesh already handles concerns - Very latency-sensitive (direct communication) - Simple internal APIs
Best Practices¶
- Keep it thin - Business logic in services, not gateway
- Avoid single point of failure - Deploy multiple instances
- Cache aggressively - Reduce backend load
- Monitor everything - Latency, errors, throughput
- Rate limit early - Protect backend services
- Use circuit breakers - Fail fast, recover gracefully
- Validate requests - Block bad requests at the edge
- Version APIs - Support multiple versions
- Log requests - For debugging and analytics
- Use async when possible - Non-blocking I/O
Monitoring & Observability¶
# Kong Prometheus plugin
plugins:
- name: prometheus
config:
status_code_metrics: true
latency_metrics: true
bandwidth_metrics: true
upstream_health_metrics: true
Key Metrics to Monitor: - Request rate - Error rate (4xx, 5xx) - Latency (p50, p95, p99) - Upstream health - Connection pool usage - Rate limit hits - Circuit breaker state - Cache hit ratio