Bastion

Forge Extension

Integrate Bastion as a Forge extension with auto-configuration and DI container binding.

Bastion ships a first-class Forge extension that handles gateway initialization, FARP discovery wiring, dashboard registration, health checks, and graceful shutdown automatically. This guide covers setup via programmatic options and YAML configuration.

Quick Start

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

app := forge.New()
app.Register(extension.New(
    bastion.WithBasePath("/api"),
    bastion.WithDashboardEnabled(true),
))
app.Run()

That single call registers Bastion into the Forge lifecycle:

  1. Loads configuration from YAML (merged with programmatic options).
  2. Initializes the proxy engine, route manager, circuit breakers, and health monitor.
  3. Auto-resolves the discovery extension from the DI container for FARP service discovery.
  4. Registers the admin REST API and dashboard routes.
  5. Provides the *bastion.Gateway instance to the DI container via Vessel.
  6. Starts the WebSocket hub for real-time dashboard updates.

Extension Lifecycle

The extension participates in the full Forge lifecycle:

MethodWhen CalledWhat Happens
Register(app)App startupLoad config, initialize components, register with DI, set up admin routes
Start(ctx)After all extensions registeredWire callbacks, load manual and persisted routes, start discovery, start health monitor
Health(ctx)Health check requestsVerify routes exist and upstream targets are reachable
Stop(ctx)Graceful shutdownSet draining mode, stop discovery, stop health monitor

The gateway declares a dependency on the discovery extension, ensuring it is always registered and started first.

YAML Configuration

Bastion reads configuration from your Forge config file under the bastion key:

# config.yaml
bastion:
  enabled: true
  base_path: "/api"

  routes:
    - path: /users
      targets:
        - url: http://user-service:8080
          weight: 1
      strip_prefix: true
      enabled: true

    - path: /orders
      targets:
        - url: http://order-service:8080
          weight: 1
      strip_prefix: true
      enabled: true

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

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

  rate_limiting:
    enabled: false
    requests_per_sec: 1000
    burst: 100

  health_check:
    enabled: true
    interval: 10s
    timeout: 5s
    path: "/_/health"

  load_balancing:
    strategy: roundRobin

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

  openapi:
    enabled: true
    path: /openapi.json
    title: "My API Gateway"

Config Resolution Order

The extension loads configuration in this order:

  1. YAML file (bastion key).
  2. Programmatic options (ConfigOption functions passed to New()).
  3. Defaults for any zero-valued fields via DefaultConfig().

YAML values and programmatic options are merged. Programmatic options override YAML when both set the same field.

Programmatic Options

All configuration can be set via ConfigOption functions:

import (
    "time"
    "github.com/xraph/bastion"
    "github.com/xraph/bastion/extension"
)

ext := extension.New(
    bastion.WithBasePath("/api"),
    bastion.WithDiscoveryEnabled(true),
    bastion.WithDiscoveryPollInterval(30 * time.Second),
    bastion.WithLoadBalancing(bastion.LoadBalancingConfig{
        Strategy: bastion.LBWeightedRoundRobin,
    }),
    bastion.WithCircuitBreaker(bastion.CircuitBreakerConfig{
        Enabled:          true,
        FailureThreshold: 5,
        ResetTimeout:     30 * time.Second,
    }),
    bastion.WithRateLimiting(bastion.RateLimitConfig{
        Enabled:        true,
        RequestsPerSec: 1000,
        Burst:          100,
    }),
    bastion.WithDashboardEnabled(true),
    bastion.WithOpenAPIEnabled(true),
)

Key Options

OptionDescription
WithBasePath(path)URL prefix for all proxied routes
WithRoute(rc)Add a single manual route
WithRoutes(routes)Set all manual routes
WithServiceRoute(name, path, url, specURL)Add a route with OpenAPI spec
WithDiscoveryEnabled(bool)Enable/disable FARP auto-discovery
WithDiscoveryPollInterval(d)Set discovery polling interval
WithDiscoveryWatchMode(bool)Use event-driven watch instead of polling
WithLoadBalancing(cfg)Set load balancing strategy
WithCircuitBreaker(cfg)Configure circuit breaker
WithRateLimiting(cfg)Configure global rate limiting
WithHealthCheck(cfg)Configure upstream health checks
WithAuth(cfg)Configure gateway authentication
WithCaching(cfg)Configure response caching
WithDashboardEnabled(bool)Enable/disable the admin dashboard
WithOpenAPI(cfg)Configure OpenAPI aggregation
WithCORS(cfg)Configure gateway-level CORS
WithTLS(cfg)Configure upstream TLS/mTLS
WithConfig(cfg)Set the complete config struct

DI Container Integration

After registration, the *bastion.Gateway instance is available from the DI container:

import "github.com/xraph/vessel"

// In another extension or AfterStart callback
gw, err := vessel.Inject[*bastion.Gateway](app.Container())
if err != nil {
    log.Fatal("bastion not registered:", err)
}

// Access gateway components
routes := gw.RouteManager().ListRoutes()
stats := gw.Snapshot()
hooks := gw.Hooks()

Auto-Resolution of Discovery Extension

Bastion automatically resolves Forge's built-in discovery extension to power FARP-based service discovery. When a Forge application includes both the discovery extension and Bastion, the gateway automatically:

  1. Resolves the discovery.Service from the DI container.
  2. Wraps it in an adapter that implements bastion.DiscoveryService.
  3. Injects it into the gateway via SetDiscoveryService().

No manual wiring is needed. If the discovery extension is not registered, Bastion logs a warning and operates with only manual routes.

Health Check Integration

The extension implements forge.Extension's Health(ctx) method. Forge automatically includes this in its aggregated health endpoint:

func (e *Gateway) Health(ctx context.Context) error {
    if e.routeManager == nil || e.routeManager.RouteCount() == 0 {
        return fmt.Errorf("no routes configured")
    }
    return e.healthMon.Health(ctx)
}

The health check verifies:

  • At least one route is configured.
  • The upstream health monitor reports healthy (at least one target is reachable).

Persistent Store Support

The extension supports optional persistent storage for routes, circuit breaker state, health history, and audit logs. Configure a store using Grove database integration:

ext := extension.New(
    bastion.WithDashboardEnabled(true),
).Configure(
    extension.WithGroveDatabase(""),  // Use default Grove DB
)

The store backend is auto-detected from the Grove driver (PostgreSQL, SQLite, or MongoDB). Routes created via the admin API are persisted and restored on restart.

Dashboard Integration

When Bastion is used alongside the Forge dashboard extension, it automatically registers itself as a dashboard contributor. The dashboard provides pages for Overview, Routes, Upstreams, Services, Traffic, Health, Circuits, API Explorer, and Config.

import (
    "github.com/xraph/forge"
    "github.com/xraph/bastion/extension"
    dashext "github.com/xraph/forge/extensions/dashboard"
)

app := forge.New()
app.Register(dashext.New())
app.Register(extension.New(
    bastion.WithDashboardEnabled(true),
))
app.Run()

Complete Example

package main

import (
    "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"
)

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

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

    // Register the dashboard extension.
    app.Register(dashext.New())

    // Register Bastion API gateway.
    app.Register(extension.New(
        bastion.WithBasePath("/api"),
        bastion.WithRoute(bastion.RouteConfig{
            Path: "/users",
            Targets: []bastion.TargetConfig{
                {URL: "http://user-service:8080", Weight: 1},
            },
            StripPrefix: true,
            Enabled:     true,
        }),
        bastion.WithDiscoveryEnabled(true),
        bastion.WithDashboardEnabled(true),
        bastion.WithOpenAPIEnabled(true),
    ))

    app.Run()
}

With the corresponding YAML config:

# config.yaml
bastion:
  enabled: true
  base_path: "/api"
  routes:
    - path: /users
      targets:
        - url: http://user-service:8080
          weight: 1
      strip_prefix: true
      enabled: true
  discovery:
    enabled: true
  dashboard:
    enabled: true
    base_path: /gateway
  openapi:
    enabled: true

On this page