# API Gateway Routing Strategy for Incremental Legacy Migration

> A deep dive into designing an API gateway routing layer for incremental legacy migration — traffic splitting, header-based routing, canary rollouts, and rollback control.

- **Published:** 2025-03-21
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** api-gateway, legacy-migration, routing, incremental-migration, deep-dive
- **Canonical URL:** https://clixo.sh/blog/legacy-migration-api-gateway-routing-strategy

The strangler fig pattern depends on one foundational piece of infrastructure: the facade layer that routes traffic between the legacy system and the new services being extracted from it. Without a well-designed routing strategy, the migration becomes fragile — either everything is on the new service (too risky) or nothing can be shifted without a full cutover. A good API gateway routing design gives you granular control over traffic distribution throughout the entire migration lifecycle.

This is a technical deep dive into what that routing layer looks like and how to design it for a real migration.

## What the Routing Layer Has to Do

The routing layer sits in front of all traffic destined for the system being migrated. Every incoming request passes through it. It is responsible for:

- Routing a request to either the legacy system or the appropriate new service
- Enabling traffic shifting — sending a percentage of requests for a given capability to the new service
- Supporting instant rollback — switching all traffic back to the legacy path within seconds without a deployment
- Being transparent to callers — clients should not need to know or care which backend handled a request
- Logging routing decisions so that traffic distribution can be monitored

The routing layer should never contain business logic. It routes. That is all it does. Business logic that leaks into the routing layer becomes a maintenance liability that outlasts the migration.

```mermaid
flowchart LR
  A[Client request] --> B[API Gateway]
  B --> C{Routing config}
  C -->|"Path or header match"| D[New service]
  C -->|"Canary percentage"| D
  C -->|No match| E[Legacy system]
  D --> F[Response to client]
  E --> F
```

## Choosing the Right Gateway Technology

The choice of gateway depends on your traffic patterns and existing infrastructure:

**NGINX or HAProxy:** The lowest-overhead option for HTTP traffic routing. Configuration is declarative and fast. Appropriate when routing can be done on URL path, HTTP method, or headers without complex logic.

**Kong, AWS API Gateway, or similar managed gateways:** Add features like authentication passthrough, rate limiting, analytics, and plugin ecosystems. Appropriate when you are already using one of these and want to avoid adding another component.

**A thin custom routing service:** When routing decisions require state (feature flags, per-tenant routing rules, A/B test logic), a small application service may be the right approach. The risk is that this service becomes complex. Keep it minimal and stateless where possible. Use a feature flag service for state rather than embedding it in the router.

## ## Routing Patterns for Incremental Migration

### Path-based routing

The simplest form. Requests to `/api/v1/notifications/` go to the new notifications service. Everything else goes to the legacy system. This works when the new service maps cleanly to a URL namespace that is distinct in the legacy system.

The advantage is simplicity and predictability. The disadvantage is inflexibility — all traffic to that path moves together, with no ability to do gradual percentage-based rollouts.

### Header-based routing

Route requests based on a header value. The migration team can set a header in internal tooling or test clients to send requests to the new service, while all other traffic stays on the legacy path. This enables dark launching — testing the new service with real production traffic without exposing it to real users.

Example: requests with `X-Route-Target: new` are sent to the new service. All other requests go to the legacy path.

### Percentage-based traffic splitting

The gateway maintains a routing weight for each capability: 0% new, 10% new, 25% new, and so on, increasing as confidence in the new service grows. The weight is stored in configuration (or a feature flag service) and can be updated without redeploying the gateway.

This is the core mechanism for gradual rollout. Combine with monitoring: set the weight to 10%, observe error rates and latency for a defined period, then increase if metrics are within bounds.

### Per-tenant or per-user routing

Route specific tenants or user accounts to the new service, regardless of the global traffic percentage. This is useful for beta programs — you can have a specific customer on the new service while everyone else is on the legacy path, which gives you real usage feedback with controlled blast radius.

This requires the gateway to resolve a tenant or user identifier from the request and look it up against a routing table. The lookup needs to be fast (in-memory or a fast cache) to avoid adding meaningful latency to every request.

## Managing Routing Configuration

Routing configuration — which paths go where, at what weight, with what exceptions — needs to be:

**Version-controlled.** Every routing configuration change should be a commit in version control, with a description of why the change was made. This creates an audit trail and makes rollback a `git revert` away.

**Deployable independently of the gateway.** The gateway should reload configuration without restarting. NGINX supports this with `nginx -s reload`. Most managed gateways support configuration updates via API.

**Observable.** Log every routing decision with enough context to reconstruct the traffic distribution at any point in time. This is what lets you confirm that traffic shifted as intended after a configuration update.

## Rollback via Routing Configuration

One of the most valuable properties of a well-designed routing layer is that rollback does not require a code deployment. If a problem is detected with the new service, routing weight is set to 0% and all traffic immediately returns to the legacy path.

This rollback needs to happen in seconds, not minutes. Test it before the migration begins. Confirm that a routing configuration update takes effect within the SLA you need for incident response.

Document the rollback procedure for every routing configuration — who can execute it, how, and how long it takes. This should be in the on-call runbook for the migrated capability, not just in the migration plan.

## Observability During the Migration Period

The routing layer creates an opportunity for migration-specific observability. Tag every request with which backend handled it (legacy or new service). This lets you:

- Compare error rates between legacy and new service at the same traffic weight
- Compare latency distributions
- Detect cases where the legacy and new service produce different responses (output comparison in shadow mode)
- Confirm that traffic distribution matches the configured weight in practice

Without this tagging, you cannot confidently attribute incidents to the migration or to unrelated changes.

---

Designing a routing layer that holds up through a multi-month migration requires careful engineering from the start. If you want experienced input on your migration infrastructure design, [talk to the Clixo team](https://clixo.sh/#contact).

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
