Skip to main content

Overview

The YARP API Gateway uses route configuration to match incoming requests and transform paths before forwarding to backend services.

Route Configuration

Routes are defined in the ReverseProxy.Routes section of appsettings.json:
{
  "ReverseProxy": {
    "Routes": {
      "catalog-route": {
        "ClusterId": "catalog-cluster",
        "Match": {
          "Path": "/catalog-service/{**catch-all}"
        },
        "Transforms": [ { "PathPattern": "{**catch-all}" } ]
      },
      "basket-route": {
        "ClusterId": "basket-cluster",
        "Match": {
          "Path": "/basket-service/{**catch-all}"
        },
        "Transforms": [ { "PathPattern": "{**catch-all}" } ]
      },
      "ordering-route": {
        "ClusterId": "ordering-cluster",
        "RateLimiterPolicy": "fixed",
        "Match": {
          "Path": "/ordering-service/{**catch-all}"
        },
        "Transforms": [ { "PathPattern": "{**catch-all}" } ]
      }
    }
  }
}

Route Components

1. Route Identifier

Each route has a unique identifier (e.g., catalog-route, basket-route):
"catalog-route": { ... }
This name is used for:
  • Configuration reference
  • Logging and diagnostics
  • Route identification in middleware

2. Cluster Assignment

The ClusterId property links the route to a backend cluster:
"ClusterId": "catalog-cluster"
This determines where matched requests are forwarded.

3. Match Conditions

The Match object defines criteria for route selection:
"Match": {
  "Path": "/catalog-service/{**catch-all}"
}

4. Transformations

The Transforms array modifies requests before forwarding:
"Transforms": [ { "PathPattern": "{**catch-all}" } ]

Path Matching

Catch-All Pattern

The {**catch-all} pattern matches zero or more path segments:
Pattern: /catalog-service/{**catch-all}

Matches:
✓ /catalog-service/
✓ /catalog-service/api/v1/products
✓ /catalog-service/api/v1/products/123
✓ /catalog-service/api/v1/products/123/reviews

Does NOT match:
✗ /catalog-service
✗ /catalog/api/v1/products
✗ /basket-service/api/v1/items

Pattern Syntax

PatternDescriptionExample
/exactExact match/catalog-service only
/prefix/{*path}Single-segment catch-all/prefix/segment
/prefix/{**path}Multi-segment catch-all/prefix/seg1/seg2/seg3
/prefix/{id}Single parameter/prefix/123
/prefix/{id}/suffixParameter with suffix/prefix/123/suffix

Path Transformation

How It Works

Path transformation strips the service prefix before forwarding:
1. Client Request:    /catalog-service/api/v1/products
2. Pattern Match:     /catalog-service/{**catch-all}
3. Captured Value:    catch-all = "api/v1/products"
4. Transform Applied: {**catch-all} → "api/v1/products"
5. Forwarded Path:    /api/v1/products
6. Final URL:         http://catalog.api:8080/api/v1/products

Transform Configuration

"Transforms": [ { "PathPattern": "{**catch-all}" } ]
Components:
  • PathPattern: The pattern used to construct the forwarded path
  • {**catch-all}: Placeholder replaced with captured path segments

Transformation Examples

Example 1: Product List

Incoming:  GET /catalog-service/api/v1/products
Pattern:   /catalog-service/{**catch-all}
Captured:  catch-all = "api/v1/products"
Transform: {**catch-all}
Result:    GET http://catalog.api:8080/api/v1/products

Example 2: Specific Product

Incoming:  GET /catalog-service/api/v1/products/550e8400-e29b-41d4-a716-446655440000
Pattern:   /catalog-service/{**catch-all}
Captured:  catch-all = "api/v1/products/550e8400-e29b-41d4-a716-446655440000"
Transform: {**catch-all}
Result:    GET http://catalog.api:8080/api/v1/products/550e8400-e29b-41d4-a716-446655440000

Example 3: Root Path

Incoming:  GET /catalog-service/
Pattern:   /catalog-service/{**catch-all}
Captured:  catch-all = ""
Transform: {**catch-all}
Result:    GET http://catalog.api:8080/

Example 4: POST with Body

Incoming:  POST /basket-service/api/v1/basket
Body:      {"customerId": "123", "items": [...]}
Pattern:   /basket-service/{**catch-all}
Captured:  catch-all = "api/v1/basket"
Transform: {**catch-all}
Result:    POST http://basket.api:8080/api/v1/basket
Body:      {"customerId": "123", "items": [...]} (unchanged)

Complete Routing Flow

Catalog Service Example

┌─────────────────────────────────────────────────────┐
│ 1. Client Request                                   │
│    GET /catalog-service/api/v1/products?page=1      │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 2. YARP Route Matching                              │
│    Pattern: /catalog-service/{**catch-all}          │
│    Match: ✓ Success                                 │
│    Captured: catch-all = "api/v1/products"          │
│    Query String: page=1 (preserved)                 │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 3. Route Properties                                 │
│    ClusterId: catalog-cluster                       │
│    RateLimiterPolicy: (none)                        │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 4. Path Transformation                              │
│    Transform: {**catch-all}                         │
│    New Path: /api/v1/products                       │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 5. Cluster Lookup                                   │
│    Cluster: catalog-cluster                         │
│    Destination: http://catalog.api:8080             │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 6. Forwarded Request                                │
│    GET http://catalog.api:8080/api/v1/products?page=1│
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 7. Backend Response                                 │
│    200 OK                                           │
│    {"products": [...]}                              │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 8. Client Response                                  │
│    200 OK                                           │
│    {"products": [...]}                              │
└─────────────────────────────────────────────────────┘

Advanced Path Transformations

Adding a Prefix

"Transforms": [
  { "PathPattern": "/api/v1/{**catch-all}" }
]
Example:
Incoming:  /catalog-service/products
Result:    /api/v1/products

Path Removal

"Transforms": [
  { "PathRemovePrefix": "/catalog-service" }
]
Example:
Incoming:  /catalog-service/api/v1/products
Result:    /api/v1/products

Static Path

"Transforms": [
  { "PathSet": "/health" }
]
Example:
Incoming:  /catalog-service/status
Result:    /health

Path Value Substitution

"Match": {
  "Path": "/catalog-service/products/{id}"
},
"Transforms": [
  { "PathPattern": "/api/v1/products/{id}" }
]
Example:
Incoming:  /catalog-service/products/123
Result:    /api/v1/products/123

Header Transformations

YARP can also transform headers:

Adding Headers

"Transforms": [
  { "PathPattern": "{**catch-all}" },
  { "RequestHeader": "X-Gateway", "Set": "YarpApiGateway" },
  { "RequestHeader": "X-Forwarded-For", "Append": "{RemoteIpAddress}" }
]

Removing Headers

"Transforms": [
  { "PathPattern": "{**catch-all}" },
  { "RequestHeaderRemove": "X-Internal-Token" }
]

Query String Handling

Query strings are automatically preserved and forwarded:
Incoming:  /catalog-service/api/v1/products?category=electronics&page=2
Forwarded: http://catalog.api:8080/api/v1/products?category=electronics&page=2

Query String Transformations

"Transforms": [
  { "PathPattern": "{**catch-all}" },
  { "QueryParameter": "version", "Set": "v1" },
  { "QueryValueParameter": "api-key", "Append": "{ApiKey}" }
]

HTTP Method Matching

By default, all HTTP methods are matched. To restrict:
"Match": {
  "Path": "/catalog-service/{**catch-all}",
  "Methods": [ "GET", "POST" ]
}

Request Header Matching

"Match": {
  "Path": "/catalog-service/{**catch-all}",
  "Headers": [
    {
      "Name": "X-Api-Version",
      "Values": [ "v1", "v2" ],
      "Mode": "ExactHeader"
    }
  ]
}

Route Priority

YARP evaluates routes in configuration order. Use Order property for explicit prioritization:
"specific-route": {
  "ClusterId": "specific-cluster",
  "Order": 1,
  "Match": {
    "Path": "/catalog-service/api/v1/featured"
  }
},
"general-route": {
  "ClusterId": "catalog-cluster",
  "Order": 2,
  "Match": {
    "Path": "/catalog-service/{**catch-all}"
  }
}
Lower Order values are evaluated first.

Route Testing

Using curl

# Test catalog route
curl -v http://localhost:8080/catalog-service/api/v1/products

# Test basket route
curl -v http://localhost:8080/basket-service/api/v1/basket/user123

# Test ordering route (rate limited)
curl -v http://localhost:8080/ordering-service/api/v1/orders

# Test with query parameters
curl -v "http://localhost:8080/catalog-service/api/v1/products?page=2&size=10"

# Test POST request
curl -X POST http://localhost:8080/basket-service/api/v1/basket \
  -H "Content-Type: application/json" \
  -d '{"customerId":"123","items":[{"productId":"p1","quantity":2}]}'

Using Browser DevTools

// Test in browser console
fetch('http://localhost:8080/catalog-service/api/v1/products')
  .then(r => r.json())
  .then(console.log);

Common Routing Patterns

API Versioning

"catalog-v1-route": {
  "ClusterId": "catalog-v1-cluster",
  "Match": {
    "Path": "/catalog-service/v1/{**catch-all}"
  },
  "Transforms": [ { "PathPattern": "/{**catch-all}" } ]
},
"catalog-v2-route": {
  "ClusterId": "catalog-v2-cluster",
  "Match": {
    "Path": "/catalog-service/v2/{**catch-all}"
  },
  "Transforms": [ { "PathPattern": "/{**catch-all}" } ]
}

Service-Specific Endpoints

"health-route": {
  "ClusterId": "catalog-cluster",
  "Match": {
    "Path": "/catalog-service/health"
  },
  "Transforms": [ { "PathSet": "/health" } ]
}

Microservice-to-Microservice

"internal-catalog-route": {
  "ClusterId": "catalog-cluster",
  "Match": {
    "Path": "/internal/catalog/{**catch-all}",
    "Headers": [
      {
        "Name": "X-Internal-Request",
        "Values": [ "true" ]
      }
    ]
  },
  "Transforms": [ { "PathPattern": "/{**catch-all}" } ]
}

Troubleshooting

Route Not Matching

  1. Check path exactly: Routes are case-sensitive by default
  2. Verify trailing slash: /catalog-service/ vs /catalog-service
  3. Check route order: More specific routes should come first
  4. Enable logging: Set "Yarp": "Debug" in logging configuration

Path Transform Issues

  1. Missing catch-all: Ensure {**catch-all} is in both Match and Transform
  2. Double slashes: Check for // in transformed paths
  3. Query strings: Verify they’re being preserved

Debugging Routes

Enable detailed YARP logging:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Yarp": "Debug",
      "Yarp.ReverseProxy": "Debug"
    }
  }
}

Build docs developers (and LLMs) love