Bastion

Full Example

Complete gateway setup with routes, discovery, load balancing, circuit breakers, and dashboard.

This guide demonstrates all key Bastion features working together in a single, runnable Go program: static routes, FARP auto-discovery, load balancing, circuit breakers, rate limiting, health checks, the admin dashboard, hooks, and OpenAPI aggregation.

Complete Gateway

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/xraph/forge"
    "github.com/xraph/bastion"
    "github.com/xraph/bastion/extension"
    "github.com/xraph/forge/extensions/discovery"
    dashext "github.com/xraph/forge/extensions/dashboard"
    "github.com/xraph/vessel"
)

func main() {
    app := forge.New()

    // ─── 1. Register discovery for FARP auto-discovery ──────
    app.Register(discovery.New())

    // ─── 2. Register the Forge dashboard ────────────────────
    app.Register(dashext.New())

    // ─── 3. Register Bastion API gateway ────────────────────
    app.Register(extension.New(
        // Base path for all proxied routes
        bastion.WithBasePath("/api"),

        // Static routes for known services
        bastion.WithRoute(bastion.RouteConfig{
            Path:        "/users",
            ServiceName: "user-service",
            Targets: []bastion.TargetConfig{
                {URL: "http://user-service-1:8080", Weight: 3},
                {URL: "http://user-service-2:8080", Weight: 2},
                {URL: "http://user-service-3:8080", Weight: 1},
            },
            StripPrefix: true,
            Enabled:     true,
            OpenAPISpec: "http://user-service-1:8080/openapi.json",
        }),
        bastion.WithRoute(bastion.RouteConfig{
            Path:        "/orders",
            ServiceName: "order-service",
            Targets: []bastion.TargetConfig{
                {URL: "http://order-service:8080", Weight: 1},
            },
            StripPrefix: true,
            Enabled:     true,
            OpenAPISpec: "http://order-service:8080/openapi.json",
        }),

        // FARP discovery -- auto-detect new services on the network
        bastion.WithDiscoveryEnabled(true),
        bastion.WithDiscoveryPollInterval(15 * time.Second),
        bastion.WithDiscoveryWatchMode(true),
        bastion.WithDiscoveryAutoPrefix(true),
        bastion.WithDiscoveryPrefixTemplate("/{{.ServiceName}}"),
        bastion.WithDiscoveryStripPrefix(true),

        // Load balancing -- weighted round-robin across targets
        bastion.WithLoadBalancing(bastion.LoadBalancingConfig{
            Strategy: bastion.LBWeightedRoundRobin,
        }),

        // Circuit breakers -- fail fast on unhealthy upstreams
        bastion.WithCircuitBreaker(bastion.CircuitBreakerConfig{
            Enabled:          true,
            FailureThreshold: 5,
            FailureWindow:    60 * time.Second,
            ResetTimeout:     30 * time.Second,
            HalfOpenMax:      3,
        }),

        // Rate limiting -- global request throttling
        bastion.WithRateLimiting(bastion.RateLimitConfig{
            Enabled:        true,
            RequestsPerSec: 1000,
            Burst:          200,
            PerClient:      true,
        }),

        // Health checks -- active probing of upstream targets
        bastion.WithHealthCheck(bastion.HealthCheckConfig{
            Enabled:              true,
            Interval:             10 * time.Second,
            Timeout:              5 * time.Second,
            Path:                 "/_/health",
            FailureThreshold:     3,
            SuccessThreshold:     2,
            EnablePassive:        true,
            PassiveFailThreshold: 5,
        }),

        // Retry -- automatic retry for transient failures
        bastion.WithRetry(bastion.RetryConfig{
            Enabled:          true,
            MaxAttempts:      3,
            Backoff:          bastion.BackoffExponential,
            InitialDelay:     100 * time.Millisecond,
            MaxDelay:         5 * time.Second,
            Multiplier:       2.0,
            Jitter:           true,
            RetryableStatus:  []int{502, 503, 504},
            RetryableMethods: []string{"GET", "HEAD", "OPTIONS"},
        }),

        // OpenAPI aggregation -- merge specs from all upstreams
        bastion.WithOpenAPI(bastion.OpenAPIConfig{
            Enabled:         true,
            Path:            "/openapi.json",
            UIPath:          "/swagger",
            Title:           "My API Gateway",
            Description:     "Unified API documentation",
            Version:         "1.0.0",
            RefreshInterval: 30 * time.Second,
            FetchTimeout:    10 * time.Second,
            MergeStrategy:   "prefix",
            EnableRootDocs:  true,
        }),

        // Admin dashboard
        bastion.WithDashboard(bastion.DashboardConfig{
            Enabled:  true,
            BasePath: "/gateway",
            Title:    "API Gateway Dashboard",
            Realtime: true,
        }),

        // CORS
        bastion.WithCORS(bastion.CORSConfig{
            Enabled:      true,
            AllowOrigins: []string{"https://app.example.com"},
            AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
            AllowHeaders: []string{"Authorization", "Content-Type"},
            MaxAge:       86400,
        }),
    ))

    // ─── 4. Register hooks for logging and alerting ─────────
    app.AfterStart(func() {
        gw, err := vessel.Inject[*bastion.Gateway](app.Container())
        if err != nil {
            log.Fatal("resolve bastion:", err)
        }

        // Log every proxied request
        gw.Hooks().OnRequest(func(r *http.Request, route *bastion.Route) error {
            log.Printf("[REQ] %s %s -> route=%s service=%s",
                r.Method, r.URL.Path, route.ID, route.ServiceName)
            return nil
        })

        // Tag responses with gateway identity
        gw.Hooks().OnResponse(func(resp *http.Response, route *bastion.Route) {
            resp.Header.Set("X-Served-By", "bastion")
            resp.Header.Set("X-Route-ID", route.ID)
        })

        // Log upstream errors
        gw.Hooks().OnError(func(err error, route *bastion.Route, w http.ResponseWriter) {
            log.Printf("[ERR] route=%s error=%v", route.Path, err)
        })

        // Alert on health changes
        gw.Hooks().OnUpstreamHealth(func(event bastion.UpstreamHealthEvent) {
            if !event.Healthy {
                log.Printf("[ALERT] Upstream %s (%s) is DOWN", event.TargetID, event.TargetURL)
            } else {
                log.Printf("[INFO] Upstream %s (%s) recovered", event.TargetID, event.TargetURL)
            }
        })

        // Log circuit breaker transitions
        gw.Hooks().OnCircuitBreak(func(targetID string, from, to bastion.CircuitState) {
            log.Printf("[CIRCUIT] %s: %s -> %s", targetID, from, to)
        })

        // Log route table changes (FARP discoveries, manual additions)
        gw.Hooks().OnRouteChange(func(event bastion.RouteEvent) {
            log.Printf("[ROUTE] %s: %s (source=%s)",
                event.Type, event.Route.Path, event.Route.Source)
        })

        // Print summary
        stats := gw.Snapshot()
        fmt.Printf("\nGateway started with %d routes and %d upstreams\n",
            stats.TotalRoutes, stats.TotalUpstreams)
        fmt.Printf("Dashboard: http://localhost:8080/gateway\n")
        fmt.Printf("API Docs:  http://localhost:8080/gateway/openapi.json\n")
        fmt.Printf("Swagger:   http://localhost:8080/gateway/swagger\n\n")
    })

    // ─── 5. Run the application ─────────────────────────────
    app.Run()
}

YAML Config Equivalent

The same gateway configuration expressed as YAML:

# config.yaml
app:
  name: api-gateway
  port: 8080

bastion:
  enabled: true
  base_path: "/api"

  routes:
    - path: /users
      service_name: user-service
      targets:
        - url: http://user-service-1:8080
          weight: 3
        - url: http://user-service-2:8080
          weight: 2
        - url: http://user-service-3:8080
          weight: 1
      strip_prefix: true
      enabled: true
      openapi_spec: http://user-service-1:8080/openapi.json

    - path: /orders
      service_name: order-service
      targets:
        - url: http://order-service:8080
          weight: 1
      strip_prefix: true
      enabled: true
      openapi_spec: http://order-service:8080/openapi.json

  discovery:
    enabled: true
    poll_interval: 15s
    watch_mode: true
    auto_prefix: true
    prefix_template: "/{{.ServiceName}}"
    strip_prefix: true

  load_balancing:
    strategy: weightedRoundRobin

  circuit_breaker:
    enabled: true
    failure_threshold: 5
    failure_window: 60s
    reset_timeout: 30s
    half_open_max: 3

  rate_limiting:
    enabled: true
    requests_per_sec: 1000
    burst: 200
    per_client: true

  health_check:
    enabled: true
    interval: 10s
    timeout: 5s
    path: "/_/health"
    failure_threshold: 3
    success_threshold: 2
    enable_passive: true
    passive_fail_threshold: 5

  retry:
    enabled: true
    max_attempts: 3
    backoff: exponential
    initial_delay: 100ms
    max_delay: 5s
    multiplier: 2.0
    jitter: true
    retryable_status: [502, 503, 504]
    retryable_methods: [GET, HEAD, OPTIONS]

  openapi:
    enabled: true
    path: /openapi.json
    ui_path: /swagger
    title: "My API Gateway"
    description: "Unified API documentation"
    version: "1.0.0"
    refresh_interval: 30s
    fetch_timeout: 10s
    merge_strategy: prefix
    enable_root_docs: true

  dashboard:
    enabled: true
    base_path: /gateway
    title: "API Gateway Dashboard"
    realtime: true

  cors:
    enabled: true
    allow_origins: ["https://app.example.com"]
    allow_methods: [GET, POST, PUT, DELETE, PATCH]
    allow_headers: [Authorization, Content-Type]
    max_age: 86400

What This Example Demonstrates

FeatureWhat Is Configured
Static RoutesTwo services (user-service, order-service) with explicit targets and weights
FARP DiscoveryAuto-detection of new services on the network with watch mode
Load BalancingWeighted round-robin distributes traffic by target weight
Circuit BreakersAuto-trip after 5 failures in 60s, half-open probe after 30s
Rate Limiting1000 req/s global with per-client tracking and burst of 200
Health ChecksActive probing every 10s, passive failure detection
RetryExponential backoff with jitter for 502/503/504 errors
OpenAPI AggregationMerged spec from all upstream services at /openapi.json
Admin DashboardReal-time dashboard at /gateway with WebSocket updates
CORSCross-origin support for the frontend application
HooksRequest logging, response tagging, error alerting, health alerts

Running the Gateway

# Build and run
go run main.go

# Or with a custom config file
CONFIG_FILE=config.yaml go run main.go

Once running:

  • Proxy endpoint: http://localhost:8080/api/* -- routes to upstream services
  • Dashboard: http://localhost:8080/gateway -- real-time admin UI
  • API docs: http://localhost:8080/gateway/openapi.json -- aggregated OpenAPI spec
  • Swagger UI: http://localhost:8080/gateway/swagger -- interactive API explorer
  • Admin API: http://localhost:8080/gateway/api/routes -- route management
  • Stats: http://localhost:8080/gateway/api/stats -- gateway statistics
  • WebSocket: ws://localhost:8080/gateway/ws -- real-time event stream

Testing the Gateway

# List all routes
curl http://localhost:8080/gateway/api/routes | jq

# Proxy a request through the gateway
curl http://localhost:8080/api/users

# Check gateway statistics
curl http://localhost:8080/gateway/api/stats | jq

# View discovered services
curl http://localhost:8080/gateway/api/discovery/services | jq

# Create a route via the admin API
curl -X POST http://localhost:8080/gateway/api/routes \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/products",
    "targets": [{"url": "http://product-service:8080", "weight": 1}],
    "stripPrefix": true,
    "enabled": true,
    "protocol": "http"
  }'

Next Steps

On this page