Skip to content

API Gateway ​

YARP-based reverse proxy providing unified entry point for all Vulcan services

Overview ​

The Vulcan API Gateway is built on YARP (Yet Another Reverse Proxy), Microsoft's high-performance reverse proxy library for .NET. It serves as the single entry point for all API traffic, routing requests to the appropriate backend microservices.

Tech Stack:

  • .NET 10
  • YARP reverse proxy
  • ASP.NET Core minimal APIs
  • Built-in rate limiting
  • Health checks aggregation

Port: 8080 (different from microservices which use port 5000)


Architecture ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         Clients                                  β”‚
β”‚     Web (React) | Mobile (React Native) | External APIs          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                               β”‚ HTTPS
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    API Gateway (Port 8080)                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  YARP    β”‚  β”‚   Rate     β”‚  β”‚ Health  β”‚  β”‚   OpenAPI    β”‚  β”‚
β”‚  β”‚  Proxy   β”‚  β”‚  Limiting  β”‚  β”‚  Check  β”‚  β”‚ Aggregation  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                      β”‚                      β”‚
        β–Ό                      β–Ό                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Auth Service β”‚      β”‚    Leads     β”‚      β”‚  Quotation   β”‚
β”‚ Port: 3000   β”‚      β”‚ Port: 5000   β”‚      β”‚ Port: 5000   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

        β”‚                      β”‚                      β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    ... and 8 more services

Key Features ​

1. Request Routing ​

Routes incoming requests to the correct microservice based on URL path.

Route Pattern:

/api/{service-name}/* β†’ http://vulcan-be-{service-name}:5000/*

Example Routes:

  • /api/auth/* β†’ http://vulcan-be-auth:3000/*
  • /api/leads/* β†’ http://vulcan-be-leads:5000/*
  • /api/quotation/* β†’ http://vulcan-be-quotation:5000/*
  • /api/contracts/* β†’ http://vulcan-be-contracts:5000/*
  • /api/projects/* β†’ http://vulcan-be-projects:5000/*
  • /api/invoicing/* β†’ http://vulcan-be-invoicing:5000/*
  • /api/documents/* β†’ http://vulcan-be-documents:5000/*
  • /api/ai/* β†’ http://vulcan-be-ai:5000/*
  • /api/planning/* β†’ http://vulcan-be-planning:5000/*
  • /api/coresetup/* β†’ http://vulcan-be-coresetup:5000/*

2. Rate Limiting ​

Protects services from abuse with configurable rate limits.

Default Policy:

  • Fixed window: 100 requests per minute per IP
  • Sliding window: For premium users (future)
  • Token bucket: For burst traffic handling

Configuration:

json
{
  "RateLimiting": {
    "PermitLimit": 100,
    "Window": "00:01:00",
    "QueueLimit": 10
  }
}

3. Health Check Aggregation ​

Combines health checks from all microservices into a single endpoint.

Endpoints:

  • GET /health - Overall system health
  • GET /health/ready - Readiness probe (Kubernetes)
  • GET /health/live - Liveness probe (Kubernetes)

Response Example:

json
{
  "status": "Healthy",
  "totalDuration": "00:00:00.234",
  "entries": {
    "auth": { "status": "Healthy", "duration": "00:00:00.012" },
    "leads": { "status": "Healthy", "duration": "00:00:00.023" },
    "quotation": { "status": "Healthy", "duration": "00:00:00.019" },
    ...
  }
}

4. OpenAPI Documentation Aggregation ​

Combines Swagger/OpenAPI specs from all microservices.

Endpoints:

  • GET /scalar/v1 - Interactive API documentation (Scalar UI)
  • GET /swagger/v1/swagger.json - Aggregated OpenAPI spec

Features:

  • Single documentation page for all services
  • Try-it-out functionality
  • Authentication integration
  • Request/response examples

5. Landing Page ​

Simple status page showing gateway health and service availability.

Features:

  • Service status indicators
  • Link to API documentation
  • Health check results
  • Version information

Configuration ​

YARP Routes ​

Configured in appsettings.json:

json
{
  "ReverseProxy": {
    "Routes": {
      "auth-route": {
        "ClusterId": "auth-cluster",
        "Match": {
          "Path": "/api/auth/{**catch-all}"
        }
      },
      "leads-route": {
        "ClusterId": "leads-cluster",
        "Match": {
          "Path": "/api/leads/{**catch-all}"
        }
      }
      // ... more routes
    },
    "Clusters": {
      "auth-cluster": {
        "Destinations": {
          "auth": {
            "Address": "http://vulcan-be-auth:3000"
          }
        }
      },
      "leads-cluster": {
        "Destinations": {
          "leads": {
            "Address": "http://vulcan-be-leads:5000"
          }
        }
      }
      // ... more clusters
    }
  }
}

Environment Variables ​

Development:

bash
ASPNETCORE_ENVIRONMENT=Development
ASPNETCORE_URLS=http://+:8080

Production:

bash
ASPNETCORE_ENVIRONMENT=Production
ASPNETCORE_URLS=http://+:8080
FORWARD_HEADERS_ENABLED=true

Adding a New Microservice ​

When adding a new microservice to Vulcan, follow these steps:

Step 1: Add Route Configuration ​

Edit appsettings.json:

json
{
  "ReverseProxy": {
    "Routes": {
      "newservice-route": {
        "ClusterId": "newservice-cluster",
        "Match": {
          "Path": "/api/newservice/{**catch-all}"
        }
      }
    }
  }
}

Step 2: Add Cluster Configuration ​

json
{
  "ReverseProxy": {
    "Clusters": {
      "newservice-cluster": {
        "Destinations": {
          "newservice": {
            "Address": "http://vulcan-be-newservice:5000"
          }
        }
      }
    }
  }
}

Step 3: Add Health Check ​

Edit Program.cs:

csharp
builder.Services.AddHealthChecks()
    .AddUrlGroup(
        new Uri("http://vulcan-be-newservice:5000/health"),
        name: "newservice",
        timeout: TimeSpan.FromSeconds(5)
    );

Step 4: Add to OpenAPI Aggregation ​

Edit Services/OpenApiAggregatorService.cs:

csharp
private static readonly Dictionary<string, string> ServiceEndpoints = new()
{
    // ... existing services
    { "newservice", "http://vulcan-be-newservice:5000/swagger/v1/swagger.json" }
};

Step 5: Deploy ​

bash
# Rebuild gateway
dotnet build src/Vulcan.Gateway

# Update Kubernetes deployment
kubectl rollout restart deployment/vulcan-be-gateway -n vulcan

# Verify routing
curl http://gateway:8080/api/newservice/health

Performance Characteristics ​

Latency ​

  • Average overhead: <5ms per request
  • P50 latency: ~2ms
  • P99 latency: ~15ms
  • Connection pooling: Enabled for all destinations

Throughput ​

  • Requests per second: 10,000+ (single instance)
  • Concurrent connections: 1,000+
  • Max body size: 100MB

Caching ​

  • Response caching: Disabled by default (microservices handle caching)
  • Connection caching: Enabled (HTTP/2)
  • DNS caching: Enabled

Security ​

Authentication ​

Gateway itself doesn't authenticate requestsβ€”it forwards them to microservices which validate JWT tokens.

Flow:

  1. Client includes Authorization: Bearer <token> header
  2. Gateway forwards header to microservice
  3. Microservice validates token and extracts user context

CORS ​

Configured to allow requests from:

  • https://app.vulcan.se (production)
  • https://staging.vulcan.se (staging)
  • http://localhost:5173 (local dev)

Configuration:

csharp
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy
            .WithOrigins(
                "https://app.vulcan.se",
                "https://staging.vulcan.se",
                "http://localhost:5173"
            )
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials();
    });
});

Rate Limiting ​

Applied at gateway level before routing to microservices.

Benefits:

  • Protects microservices from DDoS
  • Fair usage across clients
  • Prevents accidental load spikes

Headers ​

Forwarded Headers:

  • X-Forwarded-For - Client IP
  • X-Forwarded-Proto - Original protocol (HTTP/HTTPS)
  • X-Forwarded-Host - Original host

Monitoring & Observability ​

Logging ​

Structured logging with Serilog:

  • Request/response logs
  • Routing decisions
  • Health check results
  • Error logs with stack traces

Log Level:

  • Development: Debug
  • Production: Information

Metrics ​

Exported to Prometheus/Azure Monitor:

  • Request count by route
  • Request duration by route
  • Health check status
  • Rate limit rejections
  • Error rate

Distributed Tracing ​

OpenTelemetry integration (future):

  • Trace requests across gateway + microservices
  • Identify bottlenecks
  • Debug issues in production

Development ​

Local Setup ​

bash
cd repos/vulcan-be-gateway

# Restore dependencies
dotnet restore

# Run locally
dotnet run --project src/Vulcan.Gateway

# Gateway available at http://localhost:8080

Testing Routes ​

bash
# Test gateway health
curl http://localhost:8080/health

# Test auth routing
curl http://localhost:8080/api/auth/health

# Test leads routing
curl http://localhost:8080/api/leads/health

# View API docs
open http://localhost:8080/scalar/v1

Deployment ​

Docker ​

bash
# Build image
docker build -t vulcan-be-gateway .

# Run container
docker run -p 8080:8080 vulcan-be-gateway

Kubernetes ​

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vulcan-be-gateway
  namespace: vulcan
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: gateway
        image: registry.vulcan.se/vulcan-be-gateway:latest
        ports:
        - containerPort: 8080
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: "Production"
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080

Load Balancing ​

Gateway can be horizontally scaled for high availability:

              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚  Load Balancer β”‚
              β”‚ (Azure LB/NGINX)β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β–Ό             β–Ό             β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚Gateway β”‚    β”‚Gateway β”‚    β”‚Gateway β”‚
    β”‚   #1   β”‚    β”‚   #2   β”‚    β”‚   #3   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Troubleshooting ​

Gateway Returns 503 ​

Cause: Downstream service unavailable

Solution:

  1. Check health endpoint: curl http://gateway:8080/health
  2. Check service status: kubectl get pods -n vulcan
  3. Check service logs: kubectl logs -n vulcan <pod-name>

Slow Response Times ​

Cause: Downstream service slow or timeout too high

Solution:

  1. Check gateway metrics for P99 latency
  2. Profile downstream service
  3. Adjust timeout in YARP config:
    json
    {
      "Clusters": {
        "service-cluster": {
          "HttpRequest": {
            "Timeout": "00:00:30"
          }
        }
      }
    }

Rate Limit Errors (429) ​

Cause: Client exceeding rate limit

Solution:

  1. Increase rate limit for authenticated users
  2. Implement exponential backoff on client
  3. Use caching to reduce requests

Future Enhancements ​

Planned Features ​

  • [ ] JWT validation at gateway (optional)
  • [ ] Request/response transformation
  • [ ] Circuit breaker pattern
  • [ ] Advanced caching (Redis)
  • [ ] WebSocket support
  • [ ] gRPC support
  • [ ] API versioning support
  • [ ] Request deduplication
  • [ ] Response compression
  • [ ] Distributed tracing (OpenTelemetry)

Want to understand service routing? Check Architecture Overview or read about Authentication Service.

Built with VitePress | v1.2.0 | πŸš€ Week One Sprint