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
| Service | DI Key | Type |
|---|---|---|
| Gateway extension | gateway | *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:
discoveryextension -- for FARP-based auto-discovery of upstream services. - Optional:
cacheextension -- 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)
}Automatic FARP Discovery (Recommended)
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:
- Resolves
*discovery.Servicefrom the DI container. - Lists all services from the discovery backend (mDNS, Consul, etc.).
- For each service with
farp.enabled=truein its metadata, fetches the FARP manifest. - Generates proxy routes based on the service's API schemas (OpenAPI, AsyncAPI, GraphQL).
- 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
- user-service starts and registers itself via mDNS with FARP metadata in TXT records.
- api-gateway starts and the gateway extension resolves the discovery service from DI.
- The gateway discovers
user-servicevia mDNS, seesfarp.enabled=truein its metadata. - It reads
farp.openapi=http://192.168.x.x:8080/openapi.jsonfrom the metadata. - A proxy route is created at
/user-service/*that forwards tohttp://192.168.x.x:8080. - Requests to
http://gateway:8080/user-service/api/usersare proxied tohttp://user-service:8080/api/users.
Client → GET /user-service/api/users → API Gateway → user-service:8080/api/usersProduction 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?