Skip to content

Request Routing at the API Gateway

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you'll learn about Request Routing. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Request routing is the process of directing incoming API requests to the appropriate backend service based on URL path, HTTP method, headers, or other request attributes.

What You'll Learn

By the end of this lesson, you will implement path-based, header-based, and method-based routing, understand service discovery integration, and configure dynamic routes.

Why It Matters

Routing is the core function of an API Gateway. Incorrect routing causes requests to reach the wrong service, resulting in errors, data corruption, or security breaches.

Real-World Use

Kong routes /users to the user service, /products to the catalog service, and /orders to the order service, each running on different clusters.

Routing Types

flowchart TD
    Request[Incoming Request] --> Router{Router}
    Router -->|path: /users/*| User[User Service]
    Router -->|path: /products/*| Product[Product Service]
    Router -->|header: X-Region: EU| EU[EU Cluster]
    Router -->|method: POST| Create[Create Handler]
    Router -->|default| Default[Default Service]

Path-Based Routing

The most common approach: route by URL path prefix.

# path_routing.py
from typing import Dict, Optional

class PathRouter:
    def __init__(self):
        self.routes: Dict[str, dict] = {}

    def add_route(self, prefix: str, service: str, strip_prefix: bool = True):
        self.routes[prefix] = {
            "service": service,
            "strip_prefix": strip_prefix,
        }

    def route(self, path: str) -> Optional[dict]:
        sorted_prefixes = sorted(self.routes.keys(), key=len, reverse=True)

        for prefix in sorted_prefixes:
            if path.startswith(prefix):
                route_config = self.routes[prefix]
                remaining_path = path[len(prefix):] if route_config["strip_prefix"] else path
                return {
                    "service": route_config["service"],
                    "path": remaining_path or "/",
                    "original_path": path,
                }

        return None

router = PathRouter()
router.add_route("/api/v1/users", "user-service:3000")
router.add_route("/api/v1/products", "product-service:4000")
router.add_route("/api/v1/orders", "order-service:5000")

paths = ["/api/v1/users/123", "/api/v1/products", "/api/v1/orders/new", "/api/v1/admin"]
for path in paths:
    result = router.route(path)
    if result:
        print(f"{path:40s} -> {result['service']:25s} {result['path']}")
    else:
        print(f"{path:40s} -> NO ROUTE FOUND")

Expected output:

/api/v1/users/123                      -> user-service:3000        /123
/api/v1/products                       -> product-service:4000     /
/api/v1/orders/new                     -> order-service:5000       /new
/api/v1/admin                          -> NO ROUTE FOUND

Header-Based Routing

Route based on request headers, useful for canary deployments and A/B testing.

# header_routing.py
import json
from typing import Dict, Optional

class HeaderRouter:
    def __init__(self):
        self.header_routes: Dict[str, Dict[str, str]] = {}
        self.default_service: Optional[str] = None

    def add_header_route(self, header: str, value: str, service: str):
        if header not in self.header_routes:
            self.header_routes[header] = {}
        self.header_routes[header][value] = service

    def set_default(self, service: str):
        self.default_service = service

    def route(self, headers: Dict[str, str]) -> Optional[str]:
        for header_name, value_map in self.header_routes.items():
            header_value = headers.get(header_name)
            if header_value and header_value in value_map:
                return value_map[header_value]

        return self.default_service

router = HeaderRouter()
router.add_header_route("X-Canary", "v2", "user-service-v2:3000")
router.add_header_route("X-Region", "eu", "eu-cluster:3000")
router.add_header_route("X-Region", "us", "us-cluster:3000")
router.set_default("user-service-stable:3000")

test_headers = [
    {"X-Canary": "v2"},
    {"X-Region": "eu"},
    {"X-Region": "us"},
    {"Authorization": "Bearer token"},
]

for headers in test_headers:
    service = router.route(headers)
    print(f"Headers: {headers} -> {service}")

Expected output:

Headers: {'X-Canary': 'v2'} -> user-service-v2:3000
Headers: {'X-Region': 'eu'} -> eu-cluster:3000
Headers: {'X-Region': 'us'} -> us-cluster:3000
Headers: {'Authorization': 'Bearer token'} -> user-service-stable:3000

Method-Based Routing

# method_routing.py
from typing import Dict

class MethodRouter:
    def __init__(self):
        self.method_routes: Dict[str, str] = {}

    def add_route(self, method: str, service: str):
        self.method_routes[method.upper()] = service

    def route(self, method: str, path: str) -> dict:
        method = method.upper()
        if method in self.method_routes:
            return {
                "service": self.method_routes[method],
                "method": method,
                "path": path,
            }
        return {"error": f"Method {method} not supported"}

router = MethodRouter()
router.add_route("GET", "query-service:3000")
router.add_route("POST", "command-service:3000")
router.add_route("DELETE", "admin-service:3000")

for method in ["GET", "POST", "DELETE", "PATCH"]:
    result = router.route(method, "/api/resource")
    print(f"{method:10s} -> {result.get('service', 'error')}")

Expected output:

GET        -> query-service:3000
POST       -> command-service:3000
DELETE     -> admin-service:3000
PATCH      -> error

Common Mistakes

1. Longest-Prefix Matching Order

Without sorting routes by length, a short prefix like /api can match before /api/v2. Always match most specific routes first.

2. Not Stripping Prefixes

When routing /api/v1/users to a service, the service receives /api/v1/users instead of /users. Strip the prefix before forwarding.

3. Hardcoding Service Addresses

Hardcoded addresses break when services scale or redeploy. Integrate with service discovery (Consul, Kubernetes DNS).

4. Case-Sensitive Routes

URL paths can be case-insensitive in some clients. Normalize paths to lowercase before matching.

5. Routing Based on Query Parameters Only

Query parameters can be lost or reordered. Use headers or path for critical routing decisions.

Practice Questions

1. What is the most common routing Strategy used in API gateways?

Path-based routing, where URL prefixes determine which service handles the request.

2. Why should you strip the route prefix before forwarding?

Backend services expect their native paths, not gateway-prefixed paths. Stripping keeps services decoupled from gateway configuration.

3. How does header-based routing support canary deployments?

By routing a subset of traffic (based on a header) to a new version while the rest stays on the stable version.

4. What is the role of service discovery in routing?

It provides the gateway with dynamic service locations so routes work even as services scale up and down.

Challenge

Design a routing configuration for a platform with four services, three deployment regions, and a canary flag, where traffic is routed based on path, region header, and canary header.

FAQ

Can a request match multiple routes?

Yes. Most gateways use precedence rules (longest prefix match, first match) to resolve conflicts.

How does routing handle trailing slashes?

Gateways can normalize trailing slashes or treat them as different paths. Configure consistent normalization.

Can I route based on request body content?

Some gateways support body-based routing, but it is expensive. Prefer header or path-based routing.

What is a fallback route?

A default route that catches all unmatched requests. Used for custom 404 handling or routing to a default service.

How do wildcards work in routing?

Wildcards match any path segment. Example: /api/*/users matches /api/v1/users and /api/v2/users.

Mini Project: Multi-Strategy Router

# multi_router.py
import json
from typing import Dict, List, Optional

class Route:
    def __init__(self, paths: List[str], methods: List[str],
                 service: str, headers: Optional[Dict[str, str]] = None):
        self.paths = paths
        self.methods = [m.upper() for m in methods]
        self.service = service
        self.headers = headers or {}

class MultiStrategyRouter:
    def __init__(self):
        self.routes: List[Route] = []

    def add_route(self, route: Route):
        self.routes.append(route)

    def route(self, method: str, path: str, headers: Dict[str, str]) -> dict:
        method = method.upper()
        sorted_routes = sorted(self.routes,
                               key=lambda r: max(len(p) for p in r.paths),
                               reverse=True)

        for route in sorted_routes:
            if method not in route.methods:
                continue
            if not any(path.startswith(p) for p in route.paths):
                continue
            if not all(headers.get(k) == v for k, v in route.headers.items()):
                continue
            return {"service": route.service, "matched": True}

        return {"service": None, "matched": False}

router = MultiStrategyRouter()
router.add_route(Route(["/api/users"], ["GET", "POST"], "user-service"))
router.add_route(Route(["/api/orders"], ["GET"], "order-service", {"X-Region": "us"}))
router.add_route(Route(["/api/orders"], ["GET"], "order-service-eu", {"X-Region": "eu"}))

tests = [
    ("GET", "/api/users", {}),
    ("GET", "/api/orders", {"X-Region": "us"}),
    ("GET", "/api/orders", {"X-Region": "eu"}),
    ("POST", "/api/orders", {"X-Region": "us"}),
]

for method, path, headers in tests:
    r = router.route(method, path, headers)
    print(f"{method} {path} -> {r['service'] or 'NO MATCH'}")

Expected output:

GET /api/users -> user-service
GET /api/orders -> order-service
GET /api/orders -> order-service-eu
POST /api/orders -> NO MATCH

What's Next

You understand routing strategies. Next, learn about load balancing, then explore request transformation.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro