API Gateway Patterns for Microservices

May 31, 2021

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

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.