DEV Community

Anton Martyniuk
Anton Martyniuk

Posted on

YARP as an API Gateway in .NET: 7 Real-World Scenarios You Should Know

Building modern distributed applications requires a robust API Gateway to manage traffic, security, and routing.

YARP (Yet Another Reverse Proxy) is Microsoft's high-performance reverse proxy built on ASP.NET Core.
You can use it as a flexible and extensible foundation for building API Gateways in .NET.

It features high performance and integrates seamlessly with ASP.NET Core.
And it's pretty simple to set up.

If you are using Ocelot, consider that YARP provides improved performance and more efficient integration with ASP.NET Core, making it a stronger choice for .NET environments.

If you use complex API Gateways such as Traefik or Envoy, YARP can serve as a simpler solution if advanced features are unnecessary.

In this post, we will explore:

  • Getting Started with YARP
  • Using YARP as an API Gateway for Microservices
  • Load Balancing Across Service Instances
  • Centralized Authentication and Authorization
  • Request Routing and Path Rewriting
  • YARP as Backend-For-Frontend (BFF) Gateway
  • Traffic Shaping and Rate Limiting
  • Observability and Centralized Logging
  • Additional Scenarios You Might Consider

Let's dive in.

Getting Started with YARP

YARP is a reverse proxy toolkit developed by Microsoft for building fast and customizable proxy servers using ASP.NET Core infrastructure.

Unlike traditional API Gateway solutions with fixed features, YARP offers building blocks you can compose for your needs.

Why choose YARP:

  • Built on ASP.NET Core, using the same high-performance infrastructure
  • Fully extensible through middleware and custom transformations
  • Configuration can be loaded from appsettings.json, C# code, or external sources
  • Supports dynamic configuration updates without restarting the application
  • Integrates seamlessly with existing ASP.NET Core features like authentication, logging, and health checks
  • Open source and actively maintained by Microsoft
  • Available as a Docker container

Getting started with YARP is easy.

First, create a new ASP.NET Core Web API project and install the YARP NuGet package:

dotnet add package Yarp.ReverseProxy
Enter fullscreen mode Exit fullscreen mode

Here is how to configure YARP in your Program.cs file:

var builder = WebApplication.CreateBuilder(args);

// Add YARP services
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

// Map YARP routes
app.MapReverseProxy();

app.Run();
Enter fullscreen mode Exit fullscreen mode

This minimal setup lets YARP read the reverse proxy configuration from appsettings.json and sets up all the necessary routing.

Let's create a simple reverse proxy that forwards requests to a backend service.

Add this configuration to your appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ReverseProxy": {
    "Routes": {
      "products-route": {
        "ClusterId": "shipments-cluster",
        "Match": {
          "Path": "/products/{**catch-all}"
        }
      }
    },
    "Clusters": {
      "shipments-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:5001/"
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In this configuration:

  • Routes define how incoming requests are matched and which cluster handles them
  • Match.Path uses pattern matching - {**catch-all} captures everything after /products/
  • Clusters define groups of backend destinations
  • Destinations specify the actual backend service addresses

When a client sends a request to https://127.0.0.1:5000/products/123 (assuming you run your YARP application on port 5000), YARP forwards it to https://localhost:5001/products/123.

You can also configure YARP entirely in code if you prefer:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddReverseProxy()
    .LoadFromMemory(
        routes: new[]
        {
            new RouteConfig
            {
                RouteId = "products-route",
                ClusterId = "shipments-cluster",
                Match = new RouteMatch
                {
                    Path = "/products/{**catch-all}"
                }
            }
        },
        clusters: new[]
        {
            new ClusterConfig
            {
                ClusterId = "shipments-cluster",
                Destinations = new Dictionary<string, DestinationConfig>
                {
                    {
                        "destination1",
                        new DestinationConfig
                        {
                            Address = "https://localhost:5001/"
                        }
                    }
                }
            }
        });

var app = builder.Build();

app.MapReverseProxy();

app.Run();
Enter fullscreen mode Exit fullscreen mode

This approach provides identical functionality with full code control, useful for dynamic configurations.

YARP supports loading the proxy configuration from multiple sources. LoadFromConfig may be called multiple times, referencing different IConfiguration sections from different config files, or it may be combined with a different config source, such as InMemory.

In YARP, code-based configuration loaded via IProxyConfigProvider generally takes priority or can override appsettings.json.

Now that you have YARP set up, let's explore real-world scenarios where it's helpful.

API Gateway for Microservices

In a microservices architecture, you have multiple services, each handling a specific business boundary.
An API Gateway acts as a single entry point for all client requests, routing them to the appropriate backend service.

Without an API Gateway, clients need to know the address of every microservice. This creates tight coupling and makes it difficult to change service locations or add new services.

YARP solves this by providing a unified endpoint that routes requests based on path patterns.

Let's build an API Gateway for three microservices:

  • Shipment Service - manages shipments and their states (runs on port 5001)
  • Stock Service - handles stock updates (runs on port 5002)
  • User Service - manages users (runs on port 5003)

Here is the complete appsettings.json configuration:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Yarp": "Information"
    }
  },
  "ReverseProxy": {
    "Routes": {
      "products-route": {
        "ClusterId": "shipments-cluster",
        "Match": {
          "Path": "/api/shipments/{**catch-all}"
        }
      },
      "orders-route": {
        "ClusterId": "stocks-cluster",
        "Match": {
          "Path": "/api/stocks/{**catch-all}"
        }
      },
      "customers-route": {
        "ClusterId": "users-cluster",
        "Match": {
          "Path": "/api/users/{**catch-all}"
        }
      }
    },
    "Clusters": {
      "shipments-cluster": {
        "Destinations": {
          "products-service": {
            "Address": "https://localhost:5001/"
          }
        }
      },
      "stocks-cluster": {
        "Destinations": {
          "orders-service": {
            "Address": "https://localhost:5002/"
          }
        }
      },
      "users-cluster": {
        "Destinations": {
          "customers-service": {
            "Address": "https://localhost:5003/"
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

With this configuration:

  • Requests to https://gateway:5000/api/shipments/123 route to the Shipments Service
  • Requests to https://gateway:5000/api/stocks/456 route to the Stocks Service
  • Requests to https://gateway:5000/api/users/789 route to the Users Service

The {**catch-all} pattern captures the entire path after the prefix, including any additional segments and query strings.

Load Balancing Across Service Instances

When your application scales, you may need to run multiple instances of the same service to handle increased traffic.
Load balancing spreads incoming requests across these service instances to ensure no single instance becomes overwhelmed.

YARP provides built-in load balancing strategies that automatically distribute traffic across multiple destinations within a cluster.

Load Balancing Strategies

YARP supports several load balancing policies:

  • PowerOfTwoChoices (default) - picks two random destinations and chooses the one with fewer active requests
  • RoundRobin - spreads requests evenly across all destinations in sequence
  • Random - randomly selects a destination for each request
  • FirstAlphabetical - always picks the first destination alphabetically (useful for testing)
  • LeastRequests - routes to the destination with the fewest active requests

Here is how you can configure load balancing for the Shipment Service running on three instances:

{
  "ReverseProxy": {
    "Routes": {
      "products-route": {
        "ClusterId": "shipments-cluster",
        "Match": {
          "Path": "/api/shipments/{**catch-all}"
        }
      }
    },
    "Clusters": {
      "shipments-cluster": {
        "LoadBalancingPolicy": "RoundRobin",
        "Destinations": {
          "shipment-service-instance-1": {
            "Address": "https://localhost:5001/"
          },
          "shipment-service-instance-2": {
            "Address": "https://localhost:5011/"
          },
          "shipment-service-instance-3": {
            "Address": "https://localhost:5012/"
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

With this configuration, YARP distributes requests evenly across all three instances using the Round-Robin strategy.

Health checks let YARP route traffic only to healthy instances.
If an instance fails its health check, YARP automatically stops sending requests to it until it recovers.

First, install the health checks package:

dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks
Enter fullscreen mode Exit fullscreen mode

Configure health checks in appsettings.json:

{
  "ReverseProxy": {
    "Routes": {
      "products-route": {
        "ClusterId": "shipments-cluster",
        "Match": {
          "Path": "/api/shipments/{**catch-all}"
        }
      }
    },
    "Clusters": {
      "shipments-cluster": {
        "LoadBalancingPolicy": "RoundRobin",
        "HealthCheck": {
          "Active": {
            "Enabled": true,
            "Interval": "00:00:10",
            "Timeout": "00:00:05",
            "Policy": "ConsecutiveFailures",
            "Path": "/health"
          },
          "Passive": {
            "Enabled": true,
            "Policy": "TransportFailureRate",
            "ReactivationPeriod": "00:01:00"
          }
        },
        "Destinations": {
          "shipment-service-instance-1": {
            "Address": "https://localhost:5001/",
            "Health": "https://localhost:5001/health"
          },
          "shipment-service-instance-2": {
            "Address": "https://localhost:5011/",
            "Health": "https://localhost:5011/health"
          },
          "shipment-service-instance-3": {
            "Address": "https://localhost:5012/",
            "Health": "https://localhost:5012/health"
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Active Health Checks periodically send requests to the /health endpoint of each destination:

  • Interval - how often to check (every 10 seconds)
  • Timeout - how long to wait for a response (5 seconds)
  • Policy - when to mark a destination as unhealthy (after consecutive failures)
  • Path - the health check endpoint on the backend service

Passive Health Checks monitor actual traffic and mark destinations as unhealthy based on real request failures:

  • Policy - what triggers marking as unhealthy (transport failures like connection errors)
  • ReactivationPeriod - how long to wait before trying an unhealthy destination again (1 minute)

For this to work, your backend services need to expose a health check endpoint. See this article for how to add Health Checks in ASP.NET Core.

Sometimes you need to route requests from the same client to the same backend instance.
This is called session affinity or sticky sessions.

For more information, see the following article from official Microsoft documentation.


👉 Read original article on my newsletter: https://antondevtips.com/blog/yarp-as-api-gateway-in-dotnet

Top comments (0)