As microservices architectures mature, API gateways become critical infrastructure. They handle cross-cutting concerns like authentication, rate limiting, and routing. But gateway patterns vary widely, and choosing the wrong approach creates bottlenecks.
Here’s how to implement API gateways effectively.
Gateway Patterns
Edge Gateway
┌─────────────────────────────────────────────────────────────────┐
│ Clients │
│ Web, Mobile, Third-party │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Edge Gateway │
│ Authentication, Rate Limiting, SSL Termination, Routing │
└───────────────────────────┬─────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Service A│ │Service B│ │Service C│
└─────────┘ └─────────┘ └─────────┘
Backend for Frontend (BFF)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Web │ │ Mobile │ │ API │
│ Client │ │ App │ │ Partner │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Web BFF │ │Mobile │ │Partner │
│ │ │ BFF │ │ BFF │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────┐
│ Services │
└──────────────┘
Service Mesh Gateway
# Istio Gateway
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: api-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: api-cert
hosts:
- api.example.com
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: api-routes
spec:
hosts:
- api.example.com
gateways:
- api-gateway
http:
- match:
- uri:
prefix: /users
route:
- destination:
host: user-service
port:
number: 80
- match:
- uri:
prefix: /orders
route:
- destination:
host: order-service
port:
number: 80
Core Gateway Functions
Authentication and Authorization
# Kong plugin configuration
plugins:
- name: jwt
config:
claims_to_verify:
- exp
key_claim_name: kid
- name: acl
config:
allow:
- admin
- user
# Route-level auth
routes:
- name: admin-api
paths:
- /admin
plugins:
- name: acl
config:
allow:
- admin
// Custom auth middleware
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
claims, err := validateToken(token)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "user", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Rate Limiting
# Kong rate limiting
plugins:
- name: rate-limiting
config:
minute: 100
hour: 1000
policy: redis
redis_host: redis.default.svc
# Per-consumer limits
consumers:
- username: premium-partner
plugins:
- name: rate-limiting
config:
minute: 1000
hour: 10000
- username: free-tier
plugins:
- name: rate-limiting
config:
minute: 10
hour: 100
// Sliding window rate limiter
type RateLimiter struct {
redis *redis.Client
}
func (rl *RateLimiter) Allow(key string, limit int, window time.Duration) bool {
now := time.Now().UnixNano()
windowStart := now - window.Nanoseconds()
pipe := rl.redis.Pipeline()
pipe.ZRemRangeByScore(key, "0", strconv.FormatInt(windowStart, 10))
pipe.ZAdd(key, &redis.Z{Score: float64(now), Member: now})
pipe.ZCard(key)
pipe.Expire(key, window)
results, _ := pipe.Exec()
count := results[2].(*redis.IntCmd).Val()
return count <= int64(limit)
}
Request Transformation
# Transform requests for backend services
plugins:
- name: request-transformer
config:
add:
headers:
- X-Request-ID:$(uuid)
- X-Forwarded-For:$(client_ip)
remove:
headers:
- X-Internal-Only
rename:
headers:
- Authorization:X-Auth-Token
Response Aggregation
// BFF aggregation pattern
func (h *Handler) GetUserDashboard(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
var wg sync.WaitGroup
var mu sync.Mutex
result := make(map[string]interface{})
// Parallel calls to services
wg.Add(3)
go func() {
defer wg.Done()
profile, _ := h.userService.GetProfile(userID)
mu.Lock()
result["profile"] = profile
mu.Unlock()
}()
go func() {
defer wg.Done()
orders, _ := h.orderService.GetRecent(userID, 5)
mu.Lock()
result["recent_orders"] = orders
mu.Unlock()
}()
go func() {
defer wg.Done()
notifications, _ := h.notificationService.GetUnread(userID)
mu.Lock()
result["notifications"] = notifications
mu.Unlock()
}()
wg.Wait()
json.NewEncoder(w).Encode(result)
}
Gateway Technologies
Kong
# kong.yaml - declarative configuration
_format_version: "2.1"
services:
- name: user-service
url: http://user-service:8080
routes:
- name: user-routes
paths:
- /api/v1/users
strip_path: true
plugins:
- name: prometheus
- name: correlation-id
config:
header_name: X-Request-ID
- name: order-service
url: http://order-service:8080
routes:
- name: order-routes
paths:
- /api/v1/orders
strip_path: true
plugins:
- name: rate-limiting
config:
minute: 100
policy: local
Envoy
# Envoy configuration
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/users"
route:
cluster: user_service
- match:
prefix: "/orders"
route:
cluster: order_service
http_filters:
- name: envoy.filters.http.router
clusters:
- name: user_service
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: user_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: user-service
port_value: 8080
AWS API Gateway
# SAM template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn
# Throttling
MethodSettings:
- ResourcePath: "/*"
HttpMethod: "*"
ThrottlingBurstLimit: 100
ThrottlingRateLimit: 50
UserFunction:
Type: AWS::Serverless::Function
Properties:
Handler: users.handler
Runtime: nodejs14.x
Events:
GetUsers:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: GET
Advanced Patterns
Circuit Breaker at Gateway
# Envoy circuit breaker
clusters:
- name: user_service
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 100
max_pending_requests: 100
max_requests: 1000
max_retries: 3
outlier_detection:
consecutive_5xx: 5
interval: 10s
base_ejection_time: 30s
max_ejection_percent: 50
Canary Routing
# Istio weighted routing
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
Request Caching
# Kong caching plugin
plugins:
- name: proxy-cache
config:
response_code:
- 200
request_method:
- GET
- HEAD
content_type:
- application/json
cache_ttl: 300
strategy: memory
Observability
Gateway Metrics
# Prometheus metrics from gateway
metrics:
request_count:
labels: [service, route, status_code]
request_latency:
labels: [service, route]
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5]
active_connections:
labels: [service]
circuit_breaker_state:
labels: [service]
Distributed Tracing
# Kong OpenTelemetry plugin
plugins:
- name: opentelemetry
config:
endpoint: http://otel-collector:4317
headers:
- "X-Custom-Header:value"
resource_attributes:
service.name: api-gateway
deployment.environment: production
Anti-Patterns to Avoid
anti_patterns:
god_gateway:
problem: All business logic in gateway
solution: Keep gateway focused on cross-cutting concerns
no_timeout:
problem: Requests hang indefinitely
solution: Always configure timeouts at gateway
monolithic_gateway:
problem: Single point of failure
solution: Deploy multiple instances, consider regional gateways
no_observability:
problem: Can't debug issues
solution: Implement logging, metrics, tracing
complex_transformations:
problem: Gateway becomes bottleneck
solution: Keep transformations simple, move complex logic to services
Key Takeaways
- Choose gateway pattern based on client needs: edge for simple, BFF for complex
- Keep gateways focused on cross-cutting concerns, not business logic
- Implement rate limiting, authentication, and circuit breakers at gateway level
- Use service mesh gateways (Istio/Envoy) for advanced traffic management
- Kong and similar solutions work well for traditional API gateway needs
- Always configure timeouts and observability
- Deploy gateways redundantly; they’re critical infrastructure
- Consider caching at gateway for read-heavy APIs
- Test gateway configuration in staging before production
- Document your gateway patterns and configuration
API gateways are infrastructure that enables your microservices architecture. Get them right, and they fade into the background. Get them wrong, and they become a constant source of problems.