Bastion

Getting Started

Install Bastion and configure your first API gateway in under ten minutes.

This guide walks you through installing Bastion, defining routes, adding resilience features, and optionally enabling service discovery and the admin dashboard. By the end you will have a working API gateway proxying traffic to upstream services.

Prerequisites

  • Go 1.24+
  • A Go module (go mod init)
  • A Forge application (github.com/xraph/forge)

Install

go get github.com/xraph/bastion

This pulls in the root module and all sub-packages (proxy, resilience, health, discovery, security, etc.).

Step 1 -- Create a basic gateway

Register Bastion as a Forge extension with a single static route:

package main

import (
    "github.com/xraph/forge"
    "github.com/xraph/bastion"
)

func main() {
    app := forge.NewApp(forge.AppConfig{
        Name:    "api-gateway",
        Version: "1.0.0",
        Extensions: []forge.Extension{
            bastion.NewExtension(
                bastion.WithRoute(bastion.RouteConfig{
                    Path:    "/users/*",
                    Targets: []bastion.TargetConfig{
                        {URL: "http://user-service:8080", Weight: 1},
                    },
                    StripPrefix: true,
                    Protocol:    bastion.ProtocolHTTP,
                    Enabled:     true,
                }),
            ),
        },
    })
    app.Run()
}

Requests to /users/* are proxied to http://user-service:8080 with the /users prefix stripped. A request to /users/42 becomes /42 on the upstream.

Step 2 -- Add multiple routes

Call WithRoute multiple times to define additional routes:

bastion.NewExtension(
    bastion.WithRoute(bastion.RouteConfig{
        Path:    "/users/*",
        Targets: []bastion.TargetConfig{
            {URL: "http://user-service:8080", Weight: 1},
        },
        StripPrefix: true,
        Protocol:    bastion.ProtocolHTTP,
        Enabled:     true,
    }),
    bastion.WithRoute(bastion.RouteConfig{
        Path:    "/orders/*",
        Targets: []bastion.TargetConfig{
            {URL: "http://order-service:8080", Weight: 1},
        },
        StripPrefix: true,
        Protocol:    bastion.ProtocolHTTP,
        Enabled:     true,
    }),
    bastion.WithRoute(bastion.RouteConfig{
        Path:    "/notifications/*",
        Targets: []bastion.TargetConfig{
            {URL: "http://notification-service:8080", Weight: 1},
        },
        StripPrefix: true,
        Protocol:    bastion.ProtocolHTTP,
        Enabled:     true,
    }),
)

Step 3 -- Configure load balancing

Add multiple targets to a route and choose a load balancing strategy:

bastion.WithRoute(bastion.RouteConfig{
    Path: "/users/*",
    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,
    Protocol:     bastion.ProtocolHTTP,
    LoadBalancer: bastion.LoadBalancerWeightedRoundRobin,
    Enabled:      true,
}),

Available strategies:

StrategyConstantDescription
Round-robinLoadBalancerRoundRobinCycles through targets sequentially
Weighted round-robinLoadBalancerWeightedRoundRobinDistributes based on target weights
RandomLoadBalancerRandomSelects a target at random
Least connectionsLoadBalancerLeastConnSends to the target with fewest active connections
Consistent hashLoadBalancerConsistentHashRoutes based on a hash of the request (sticky sessions)

Step 4 -- Enable circuit breakers

Add circuit breaker protection to prevent cascading failures:

bastion.NewExtension(
    bastion.WithCircuitBreaker(bastion.CircuitBreakerConfig{
        Enabled:          true,
        FailureThreshold: 5,
        ResetTimeout:     30 * time.Second,
    }),
    bastion.WithRoute(bastion.RouteConfig{
        Path:    "/users/*",
        Targets: []bastion.TargetConfig{
            {URL: "http://user-service:8080", Weight: 1},
        },
        StripPrefix: true,
        Protocol:    bastion.ProtocolHTTP,
        Enabled:     true,
    }),
)

When a target accumulates 5 consecutive failures, the circuit opens and requests are rejected immediately. After 30 seconds the circuit enters half-open state and allows a single probe request. If it succeeds, the circuit closes. If it fails, the circuit re-opens.

Step 5 -- Add rate limiting

Protect upstreams from traffic spikes with token-bucket rate limiting:

bastion.NewExtension(
    bastion.WithRateLimiting(bastion.RateLimitConfig{
        Enabled:        true,
        RequestsPerSec: 1000,
        Burst:          100,
    }),
    // ... routes
)

This applies a global rate limit. For per-route limits, set RateLimit on individual RouteConfig entries. Per-client rate limiting uses the client IP address as the bucket key.

Step 6 -- Enable health checks

Active health probes verify upstream availability before routing traffic:

bastion.NewExtension(
    bastion.WithHealthCheck(bastion.HealthCheckConfig{
        Enabled:  true,
        Interval: 10 * time.Second,
        Path:     "/health",
    }),
    // ... routes
)

Bastion sends HTTP GET requests to the configured path on each target at the specified interval. Targets that fail health checks are marked unhealthy and excluded from the load balancer pool. They are automatically re-added when health checks pass again.

Step 7 -- YAML configuration

Instead of programmatic configuration, you can define the entire gateway in YAML:

bastion:
  enabled: true
  basePath: "/api"
  routes:
    - path: "/users/*"
      targets:
        - url: "http://user-service:8080"
          weight: 1
      stripPrefix: true
      protocol: http
      enabled: true
  circuitBreaker:
    enabled: true
    failureThreshold: 5
    resetTimeout: 30s
  rateLimiting:
    enabled: true
    requestsPerSec: 1000
    burst: 100
  healthCheck:
    enabled: true
    interval: 10s
    path: "/health"

When using YAML, the Forge application loads configuration automatically. See the Configuration page for the full key reference.

Step 8 -- Integrate with FARP discovery (optional)

If your upstream services register themselves via FARP (Forge's service discovery), Bastion can discover them automatically:

bastion.NewExtension(
    bastion.WithDiscovery(bastion.DiscoveryConfig{
        Enabled:    true,
        WatchMode:  true,
        AutoPrefix: true,
    }),
    // No manual routes needed -- discovered services are registered automatically
)

Or via YAML:

bastion:
  discovery:
    enabled: true
    watchMode: true
    autoPrefix: true

With watchMode, Bastion monitors FARP for service registrations and deregistrations in real time. With autoPrefix, each discovered service gets a route prefix matching its service name (e.g., a service named users gets the route /users/*).

Discovered routes are merged with any manually configured routes. Manual routes take precedence when paths overlap.

Step 9 -- Enable the admin dashboard (optional)

Bastion ships with a ForgeUI-based admin dashboard and REST API:

bastion.NewExtension(
    bastion.WithDashboard(bastion.DashboardConfig{
        Enabled:  true,
        BasePath: "/bastion",
        Realtime: true,
    }),
    // ... routes
)

Or via YAML:

bastion:
  dashboard:
    enabled: true
    basePath: "/bastion"
    realtime: true

This exposes the dashboard at /bastion, the admin REST API at /bastion/api/*, an aggregated OpenAPI spec at /bastion/openapi.json, a Swagger UI at /bastion/swagger, and a real-time WebSocket event stream at /bastion/ws.

Next steps

  • Architecture -- Understand the request pipeline, middleware chain, and extension lifecycle.
  • Routes -- Path matching, protocol selection, and per-route overrides.
  • Upstreams -- Target configuration, health states, and connection pooling.
  • Protocols -- HTTP, WebSocket, SSE, and gRPC proxying.
  • Configuration -- Full configuration reference with YAML keys and hot-reload behavior.

On this page