API Gateway Patterns

API Gateway Request Routing: Path, Header, and Content-Based

How to route API requests to the correct backend service — path-based routing, header-based routing, content-based routing, and traffic splitting for canary deployments.

Why Routing Belongs at the Gateway

Request routing is the core function of an API gateway. The gateway inspects each incoming request and determines which backend service should handle it. Centralizing routing in the gateway means that:

  • Clients use a single stable hostname (api.example.com) regardless of how many backend services exist or how they are reorganized
  • Service boundaries can change without requiring client updates
  • Traffic can be split between service versions for canary deployments
  • A new microservice can be introduced by simply adding a routing rule

Path-Based Routing

The most common routing strategy: match the URL path prefix to a backend service.

GET /api/v1/users/123      → users-service
GET /api/v1/orders/456     → orders-service
POST /api/v1/payments      → payments-service
GET /api/v1/products/789   → catalog-service

Prefix Stripping

Gateways often strip the routing prefix before forwarding, so backend services receive clean paths without the /api/v1 namespace:

# Kong route configuration
routes:
  - name: users-route
    paths: ["/api/v1/users"]
    strip_path: true           # /api/v1/users/123 → /users/123 upstream
    service: users-service
# Envoy route config with prefix rewrite
routes:
  - match:
      prefix: "/api/v1/users"
    route:
      cluster: users_service
      prefix_rewrite: "/users"  # strip /api/v1

Regex Matching

Use regex for more complex path patterns, such as routing by resource type regardless of the specific ID:

# AWS API Gateway resource path
paths:
  /users/{userId}:
    get:
      x-amazon-apigateway-integration:
        uri: http://users.internal/{userId}
        requestParameters:
          integration.request.path.userId: method.request.path.userId

Header-Based Routing

Header-based routing matches request headers to determine the backend. This enables versioning without changing URLs and multi-tenant routing.

API Version via Accept Header

Content negotiation-style versioning uses the Accept header:

GET /api/users
Accept: application/vnd.example.v2+json
→ routed to users-service-v2

GET /api/users
Accept: application/vnd.example.v1+json
→ routed to users-service-v1
# Envoy header-based routing
routes:
  - match:
      prefix: "/api/users"
      headers:
        - name: accept
          string_match:
            contains: "vnd.example.v2"
    route:
      cluster: users_service_v2
  - match:
      prefix: "/api/users"
    route:
      cluster: users_service_v1  # default

Tenant Routing via Custom Header

Multi-tenant SaaS applications often route to tenant-specific backends using a custom header or subdomain:

X-Tenant-ID: enterprise-corp
→ routed to dedicated enterprise-corp cluster

X-Tenant-ID: startup-inc
→ routed to shared tenant pool

Content-Based Routing

Content-based routing inspects the request body or operation to determine the backend. This is more expensive (requires body parsing) but necessary for some protocols.

GraphQL Operation Routing

Route GraphQL requests to different backends based on the operation type or name:

-- Kong Lua plugin: route GraphQL mutations to write cluster
local body = kong.request.get_body()
if body and body:find('mutation') then
    kong.service.set_target('graphql-write.internal', 4000)
else
    kong.service.set_target('graphql-read.internal', 4000)
end

gRPC Service and Method Routing

gRPC requests encode the service and method in the :path pseudo-header as /package.Service/Method. The gateway can route based on this:

# Envoy gRPC-specific routing
routes:
  - match:
      prefix: "/example.UserService/"
    route:
      cluster: user_service_grpc
  - match:
      prefix: "/example.OrderService/"
    route:
      cluster: order_service_grpc

Traffic Splitting

Traffic splitting sends a percentage of requests to one backend and the remainder to another. This is the mechanism for canary deployments and A/B testing.

Canary Deployment (Weight-Based)

Gradually shift traffic from the current version (v1) to the new version (v2):

# Envoy weighted cluster routing
routes:
  - match:
      prefix: "/api/"
    route:
      weighted_clusters:
        clusters:
          - name: api_v1
            weight: 90
          - name: api_v2
            weight: 10   # canary: 10% of traffic
        total_weight: 100
# Kong canary plugin
plugins:
  - name: canary
    config:
      percentage: 10         # 10% to canary
      upstream_host: api-v2.internal
      upstream_port: 8080

Sticky Sessions for A/B Testing

For A/B tests, you want users consistently routed to the same variant. Use a hash of a stable identifier (user ID, session cookie) to assign users:

# Envoy hash-based routing (sticky variant assignment)
route:
  weighted_clusters:
    clusters:
      - name: variant_a
        weight: 50
      - name: variant_b
        weight: 50
  hash_policy:
    - header:
        header_name: x-user-id  # hash on user ID for sticky assignment

Feature Flag Routing

Route based on a feature flag header set by the client or a feature flag service:

X-Feature-Flag: new-checkout-flow
→ routed to checkout-v2 service

Summary

Path-based routing is the foundation — map URL prefixes to services with prefix stripping so backends receive clean paths. Add header-based routing for API versioning and multi-tenancy. Use content-based routing only when necessary (GraphQL, gRPC method routing) because it requires body parsing. Traffic splitting via weighted clusters is the safest way to roll out new service versions — start at 1-5%, monitor error rates, and promote gradually.

Related Protocols

Related Glossary Terms

More in API Gateway Patterns