Gateway

Gateway

API gateway with FARP service discovery, multi-protocol proxying, and admin dashboard

Deprecated

The Gateway extension is deprecated and will be removed in a future release. Please migrate to Bastion, which provides a more robust API gateway with improved performance, enhanced security, and better scalability for production workloads.

Overview

github.com/xraph/forge/extensions/gateway provides a full-featured API gateway that can auto-discover upstream services via FARP, proxy HTTP/WebSocket/SSE/gRPC traffic, and manage routes through an admin dashboard. It includes load balancing, circuit breakers, rate limiting, health monitoring, response caching, TLS/mTLS, and OpenAPI aggregation.

What It Registers

ServiceDI KeyType
Gateway extensiongateway*Extension

The extension registers itself as a value in the DI container and mounts proxy and admin routes on the Forge HTTP router during Start().

Dependencies

  • Optional: discovery extension -- for FARP-based auto-discovery of upstream services.
  • Optional: cache extension -- for response caching.

Quick Start

Manual Routes

Configure static routes directly:

package main

import (
    "context"

    "github.com/xraph/forge"
    "github.com/xraph/forge/extensions/gateway"
)

func main() {
    app := forge.NewApp(forge.AppConfig{Name: "api-gateway", Version: "1.0.0"})

    app.RegisterExtension(gateway.NewExtension(
        gateway.WithEnabled(true),
        gateway.WithBasePath("/gw"),
        gateway.WithRoute(gateway.RouteConfig{
            Path:    "/api/users/*",
            Methods: []string{"GET", "POST", "PUT", "DELETE"},
            Targets: []gateway.TargetConfig{
                {URL: "http://user-service:8080", Weight: 100},
            },
            StripPrefix: "/api",
        }),
        gateway.WithRoute(gateway.RouteConfig{
            Path:    "/api/orders/*",
            Targets: []gateway.TargetConfig{
                {URL: "http://order-service:8080", Weight: 70},
                {URL: "http://order-service-v2:8080", Weight: 30},
            },
        }),
    ))

    ctx := context.Background()
    app.Start(ctx)
    defer app.Stop(ctx)
}

Register both the discovery and gateway extensions. The gateway automatically resolves the discovery service from the DI container, discovers upstream services via FARP metadata, and generates proxy routes -- no manual route configuration needed:

package main

import (
    "context"

    "github.com/xraph/forge"
    "github.com/xraph/forge/extensions/discovery"
    "github.com/xraph/forge/extensions/gateway"
)

func main() {
    app := forge.NewApp(forge.AppConfig{Name: "api-gateway", Version: "1.0.0"})

    // 1. Register discovery to find services on the network.
    app.RegisterExtension(discovery.NewExtension(
        discovery.WithEnabled(true),
        discovery.WithBackend("mdns"),
        discovery.WithServiceName("api-gateway"),
    ))

    // 2. Register gateway -- it auto-connects to the discovery extension.
    app.RegisterExtension(gateway.NewExtension(
        gateway.WithEnabled(true),
        gateway.WithDiscoveryEnabled(true),
        gateway.WithDiscoveryWatchMode(true),
        gateway.WithDiscoveryAutoPrefix(true),
        gateway.WithDiscoveryStripPrefix(true),
    ))

    ctx := context.Background()
    app.Start(ctx)  // Discovers FARP-enabled services and creates proxy routes.
    defer app.Stop(ctx)
}

When the gateway starts, it:

  1. Resolves *discovery.Service from the DI container.
  2. Lists all services from the discovery backend (mDNS, Consul, etc.).
  3. For each service with farp.enabled=true in its metadata, fetches the FARP manifest.
  4. Generates proxy routes based on the service's API schemas (OpenAPI, AsyncAPI, GraphQL).
  5. Watches for new services or service changes and updates routes automatically.

End-to-End FARP Example

This example shows two separate Forge applications -- a user-service and an api-gateway -- that discover each other over the local network using mDNS + FARP with zero manual route configuration.

Service Application (user-service)

package main

import (
    "context"

    "github.com/xraph/forge"
    "github.com/xraph/forge/extensions/discovery"
)

func main() {
    app := forge.NewApp(forge.AppConfig{
        Name:    "user-service",
        Version: "1.0.0",
    })

    // Register the discovery extension with mDNS + FARP.
    app.RegisterExtension(discovery.NewExtension(
        discovery.WithEnabled(true),
        discovery.WithBackend("mdns"),
        discovery.WithServiceName("user-service"),

        // Enable FARP: publishes API schema metadata in mDNS TXT records.
        discovery.WithFARPEnabled(true),
        discovery.WithFARPAutoRegister(true),
        discovery.WithFARPEndpoints(discovery.FARPEndpointsConfig{
            OpenAPI: "/openapi.json",
            Health:  "/health",
        }),
        discovery.WithFARPCapabilities("http", "rest"),
    ))

    // Register your HTTP routes as usual.
    app.Router().GET("/openapi.json", handleOpenAPISpec)
    app.Router().GET("/health", handleHealth)
    app.Router().GET("/api/users", handleListUsers)
    app.Router().GET("/api/users/:id", handleGetUser)
    app.Router().POST("/api/users", handleCreateUser)

    ctx := context.Background()
    app.Start(ctx)
    defer app.Stop(ctx)
}

Gateway Application (api-gateway)

package main

import (
    "context"

    "github.com/xraph/forge"
    "github.com/xraph/forge/extensions/discovery"
    "github.com/xraph/forge/extensions/gateway"
)

func main() {
    app := forge.NewApp(forge.AppConfig{
        Name:    "api-gateway",
        Version: "1.0.0",
    })

    // Discovery: find services on the local network via mDNS.
    app.RegisterExtension(discovery.NewExtension(
        discovery.WithEnabled(true),
        discovery.WithBackend("mdns"),
        discovery.WithServiceName("api-gateway"),
    ))

    // Gateway: auto-discover FARP services and create proxy routes.
    app.RegisterExtension(gateway.NewExtension(
        gateway.WithEnabled(true),
        gateway.WithDiscoveryEnabled(true),
        gateway.WithDiscoveryWatchMode(true),
        gateway.WithDiscoveryAutoPrefix(true),
        gateway.WithDiscoveryStripPrefix(true),
        gateway.WithDashboard(gateway.DashboardConfig{Enabled: true}),
    ))

    ctx := context.Background()
    app.Start(ctx)
    defer app.Stop(ctx)
}

What Happens at Runtime

  1. user-service starts and registers itself via mDNS with FARP metadata in TXT records.
  2. api-gateway starts and the gateway extension resolves the discovery service from DI.
  3. The gateway discovers user-service via mDNS, sees farp.enabled=true in its metadata.
  4. It reads farp.openapi=http://192.168.x.x:8080/openapi.json from the metadata.
  5. A proxy route is created at /user-service/* that forwards to http://192.168.x.x:8080.
  6. Requests to http://gateway:8080/user-service/api/users are proxied to http://user-service:8080/api/users.
Client → GET /user-service/api/users → API Gateway → user-service:8080/api/users

Production Variant (Consul)

Replace mdns with consul for production. The rest of the code stays the same:

// Service
discovery.NewExtension(
    discovery.WithEnabled(true),
    discovery.WithConsul("consul.internal:8500", ""),
    discovery.WithServiceName("user-service"),
    discovery.WithServiceTags("api", "v2"),
    discovery.WithFARPEnabled(true),
    discovery.WithFARPAutoRegister(true),
    discovery.WithFARPEndpoints(discovery.FARPEndpointsConfig{
        OpenAPI: "/openapi.json",
        Health:  "/health",
    }),
)

// Gateway
discovery.NewExtension(
    discovery.WithEnabled(true),
    discovery.WithConsul("consul.internal:8500", ""),
    discovery.WithServiceName("api-gateway"),
)
gateway.NewExtension(
    gateway.WithEnabled(true),
    gateway.WithDiscoveryEnabled(true),
    gateway.WithDiscoveryWatchMode(true),
    gateway.WithDiscoveryServiceFilters(
        gateway.ServiceFilter{
            IncludeTags: []string{"api"},
            RequireMetadata: map[string]string{"farp.enabled": "true"},
        },
    ),
)

Dynamic Route Management

Add, update, and remove routes at runtime:

ext, _ := forge.InjectType[*gateway.Extension](app.Container())
rm := ext.RouteManager()

// Add a route dynamically
rm.AddRoute(&gateway.Route{
    ID:      "payments-v1",
    Path:    "/api/payments/*",
    Methods: []string{"POST"},
    Targets: []*gateway.Target{{URL: "http://payments:8080", Weight: 100}},
})

// Listen for route changes
rm.OnRouteChange(func(event string, route *gateway.Route) {
    log.Printf("Route %s: %s %s", event, route.ID, route.Path)
})

Request Lifecycle Hooks

hooks := ext.Hooks()

hooks.OnRequest(func(r *http.Request, route *gateway.Route) (*http.Request, error) {
    r.Header.Set("X-Gateway-ID", "my-gateway")
    return r, nil
})

hooks.OnResponse(func(resp *http.Response, route *gateway.Route) *http.Response {
    resp.Header.Set("X-Proxy", "forge-gateway")
    return resp
})

hooks.OnError(func(err error, r *http.Request, route *gateway.Route) {
    log.Printf("Proxy error: %v for %s", err, r.URL.Path)
})

Auth Provider Registration

auth := ext.Auth()
auth.RegisterProvider(&myJWTAuthProvider{})

Key Concepts

  • FARP discovery -- automatically discovers upstream services via the discovery extension, fetches their FARP manifests, and generates proxy routes.
  • Multi-protocol proxy -- proxies HTTP, WebSocket, SSE, and gRPC traffic to upstream services.
  • Route management -- routes can be configured statically, discovered via FARP, or managed through the admin REST API.
  • Load balancing -- round-robin, weighted round-robin, random, least-connections, and consistent hash strategies.
  • Circuit breakers -- per-upstream three-state circuit breakers with configurable thresholds.
  • Rate limiting -- token-bucket rate limiting at global, per-route, and per-client levels.
  • Health monitoring -- active HTTP probes and passive failure tracking.
  • Admin dashboard -- ForgeUI-based dashboard with real-time WebSocket updates.
  • OpenAPI aggregation -- unified OpenAPI spec from all upstream services with Swagger UI.

Detailed Pages

How is this guide?

On this page