GraphQL Federation BFF (Backend-for-Frontend) platform service built in Rust with Axum. Hanabi sits between frontend applications and backend microservices, providing a unified GraphQL endpoint with built-in federation query planning, OAuth authentication, session management, rate limiting, WebSocket subscriptions, webhook processing, and static file serving. A single Hanabi deployment can serve multiple products via X-Product header routing.
┌────────────────────────────────────────────────────────┐
│ Hanabi BFF │
┌───────────┐ │ │
│ Browser │─── HTTP ──▶│ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ (SPA) │◀───────────│ │ Security │ │ Rate │ │ Session │ │
└───────────┘ │ │ Headers │ │ Limiter │ │ Auth │ │
│ │ CSP/CORS │ │ (Governor)│ │ (Redis) │ │
│ │ └──────────┘ └───────────┘ └──────────┘ │
│ │ │
│ WebSocket │ ┌────────────────────────────────────────────┐ │
└──────────────────│ │ Federation Query Planner │ │
│ │ (Hive Router compatible, APQ, dedup) │ │
│ └──────────┬──────────┬──────────┬──────────┘ │
│ │ │ │ │
│ ┌──────────▼──┐ ┌────▼────┐ ┌─▼──────────────┐ │
│ │ L1 Cache │ │ L2 │ │ Plan Executor │ │
│ │ (Moka) │ │ (Redis) │ │ (parallel) │ │
│ └─────────────┘ └─────────┘ └────────────────┘ │
│ │
│ ┌──────────┐ ┌────────────┐ ┌─────────────────┐ │
│ │ Webhooks │ │ Image │ │ Static Files │ │
│ │ Stripe │ │ Proxy │ │ (SPA + Brotli) │ │
│ │ Meta │ │ (S3) │ │ │ │
│ └──────────┘ └────────────┘ └─────────────────┘ │
└──────────────┬──────────┬──────────┬─────────────────┘
│ │ │
┌──────────────▼──┐ ┌─────▼────┐ ┌──▼─────────────┐
│ Subgraph A │ │ Subgraph │ │ Subgraph C │
│ (e.g. users) │ │ B │ │ (e.g. chat) │
└─────────────────┘ └──────────┘ └────────────────┘
Every GraphQL request flows through a multi-stage pipeline:
- Rate Limiter -- per-user, per-operation, per-subgraph limits (Governor + tower)
- Plugin Pre-Exec -- tracing and metrics hooks
- APQ Lookup -- resolve persisted query hash to full query text
- Security -- depth/complexity validation
- Deduplication -- coalesce concurrent identical queries
- Cache Check -- L1 (Moka in-memory) then L2 (Redis) lookup
- Query Planner -- create execution plan (Hive Router compatible, cached)
- Plan Executor -- execute against subgraphs in parallel
- Response Merge -- combine subgraph responses, entity resolution
Hanabi runs two TCP listeners:
- Port 80 (configurable) -- HTTP application server (GraphQL, OAuth, static files, webhooks)
- Port 8080 (configurable) -- Health probes (
/health/startup,/health/live,/health/ready) and Prometheus metrics (/metrics)
- GraphQL Federation -- built-in query planner (Hive Router compatible) with automatic subgraph discovery, query deduplication, and automatic persisted queries (APQ)
- BFF Pattern -- proxies GraphQL traffic to backend services with response caching, request coalescing, and adaptive load shedding
- Multi-Product Support -- single deployment serves multiple products via
X-Productheader routing - Composable Builder API --
ServerBuilderlets products register OAuth providers, webhook handlers, route extensions, and custom middleware without forking - OAuth Integration -- Google and Instagram OAuth flows with PKCE, token encryption (AES-256-GCM), and Redis session management
- WebSocket Proxy -- transparent WebSocket forwarding for GraphQL subscriptions with connection pooling and automatic reconnection
- Rate Limiting -- per-product, per-operation rate limiting with JWT-based exemptions and adaptive load shedding
- Webhook Processing -- Stripe and Meta webhook verification (HMAC signatures) and routing
- Image Proxy -- authenticated S3/MinIO image proxy with per-product bucket routing
- Federation Cache -- two-tier cache (Moka L1 + Redis L2) with NATS-based cross-pod invalidation
- Security Headers -- configurable CSP, CORS, HSTS, X-Frame-Options, Referrer-Policy, and Permissions-Policy
- Health Checks -- liveness, readiness, and startup probes with dependency health aggregation (disk, memory, Redis, NATS)
- Observability -- structured JSON logging, Prometheus metrics, optional OpenTelemetry tracing (OTLP), and Discord/Grafana startup notifications
- Static File Serving -- serves SPA frontend builds from S3 (downloaded at startup) or local directory with Brotli/gzip compression
- Geolocation -- request geolocation via configurable provider
- jemalloc -- optional jemalloc allocator for improved multi-threaded allocation performance
- Nix-native builds -- reproducible Docker images via substrate's
buildRustServicepattern
# Build
cargo build
# Run with default config
cargo run
# Run with custom config
CONFIG_PATH=config/example.yaml cargo run
# Run with jemalloc (recommended for production)
cargo build --release --features jemalloc
# Run tests
cargo testHanabi is configured via YAML files. See config/README.md for the full reference.
cp config/example.yaml config/local.yaml
# Edit config/local.yaml with your values| Variable | Description | Default |
|---|---|---|
CONFIG_PATH |
Path to config YAML | /etc/hanabi/config.yaml |
RUST_LOG |
Log level filter | info,hanabi=debug |
LOG_FORMAT |
Log format (json or pretty) |
json |
BFF_PRODUCT |
Product identifier | default |
REDIS_URL |
Redis connection URL | (optional) |
NATS_URL |
NATS connection URL | (optional) |
HMAC_SECRET |
HMAC secret for supergraph admin API | (optional) |
| Section | Purpose |
|---|---|
security |
CSP, CORS, HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy |
api |
GraphQL and WebSocket endpoint paths |
bff |
BFF mode, federation settings, cache, rate limiting, session, webhooks, WebSocket tuning |
server |
Ports, bind address, timeouts, static directory, worker threads |
compression |
Brotli/gzip settings |
cache |
Static asset and HTML cache-control headers |
health |
Disk and memory warning/critical thresholds |
metrics |
StatsD/Vector host, port, prefix |
logging |
Format, level, module-level overrides |
features |
Feature flags (bug reports, metrics, health checks, BFF) |
Mount configuration via ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: hanabi-config
data:
config.yaml: |
environment: production
bff:
federation:
enabled: true
supergraph_url: file:///etc/supergraph.graphql
server:
http_port: 80
health_port: 8080# Regenerate Cargo.nix after dependency changes
nix run .#regen
# Build the binary
nix build .#hanabi
# Build Docker images (multi-arch: amd64 + arm64)
nix build .#hanabi-image
# Push to registry
nix run .#releaseThe flake uses substrate's rust-service-flake.nix pattern for reproducible multi-architecture Docker images published to ghcr.io/pleme-io/hanabi.
Products extend Hanabi without forking by using the ServerBuilder API:
use hanabi::builder::ServerBuilder;
use hanabi::config::AppConfig;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = AppConfig::load()?;
let server = ServerBuilder::new(config)
.with_oauth(MyOAuthProvider::new())
.with_webhook(MyWebhookHandler::new())
.with_routes(my_custom_routes())
.disable_middleware(CoreMiddleware::SpaFallback)
.build()
.await;
server.run().await
}Extension points:
- OAuthProvider trait -- register custom OAuth flows (routes + callbacks)
- WebhookHandler trait -- register webhook verification and handling
- RouteExtension trait -- add arbitrary Axum routes
- MiddlewareCustomization -- disable core middleware, inject custom layers at Outermost/BeforeRouteHandling/Innermost slots
| Flag | Description | Default |
|---|---|---|
jemalloc |
Use jemalloc global allocator | off |
otel |
OpenTelemetry distributed tracing (OTLP) | off |
google-oauth |
Google OAuth provider routes | on |
instagram-oauth |
Instagram OAuth provider routes | on |
stripe-webhooks |
Stripe/PIX webhook routes | on |
meta-webhooks |
Meta webhook routes | on |
geolocation |
Geolocation endpoint | on |
image-proxy |
S3 image proxy endpoint | on |
# Build
cargo build
# Run tests (unit + integration)
cargo test
# Run federation-specific tests
cargo test --test query_planning_test
cargo test --test entity_resolution_test
cargo test --test federation_edge_cases_test
cargo test --test schema_validation_test
cargo test --test subscription_routing_test
# Lint
cargo clippy
# Load testing (requires k6)
k6 run load-tests/smoke-test.js
k6 run load-tests/graphql-load-test.js★ One module is DARK —
src/mesh/. It compiles, it has tests, and nothing constructs it; it also duplicates the federation circuit breaker that actually runs, so it should be retired rather than wired.
src/proxy/andsrc/l4/were both dark until 2026-09-24 and are now WIRED — proxy fromconfig.proxy(carrying WebSocket upgrades), l4 fromconfig.l4(raw TCP, for things no L7 proxy can carry). Both default to off, so an existing config behaves exactly as before.All three were absent from this table until 2026-09-24, which meant a reader of the README could not learn they exist and a reader of
lib.rscould not learn they were unreached. A stale doc elsewhere then described hanabi as those three modules, and an agent designed a fleet front door on that picture.Each row below states its tier,
tests/dark_modules_test.rsenforces it in both directions, and each module's header carries the detail.
| Path | Purpose |
|---|---|
src/main.rs |
Binary entry point, tokio runtime setup |
src/lib.rs |
Library root, public module re-exports |
src/builder.rs |
ServerBuilder for composable BFF assembly |
src/server.rs |
Server lifecycle (listener binding, dual-port, graceful shutdown) |
src/router.rs |
Router construction, middleware stack, route registration |
src/state.rs |
AppState shared across all handlers |
src/config/ |
YAML config parsing and validation |
src/federation/ |
GraphQL federation (query planner, executor, cache, dedup, APQ, subscriptions) |
src/auth/ |
OAuth flows (Google, Instagram), session management, middleware |
src/bff.rs |
GraphQL proxy handler, supergraph admin endpoints |
src/rate_limiting/ |
Per-operation rate limiting with JWT exemptions |
src/webhooks/ |
Stripe and Meta webhook handlers |
src/health.rs |
Liveness, readiness, startup probes |
src/health_aggregator.rs |
Dependency health aggregation |
src/images.rs |
Authenticated S3 image proxy |
src/handlers.rs |
Bug reports, telemetry, upload proxy, SPA fallback |
src/middleware.rs |
CORS, security headers, cache-control, request metrics |
src/metrics.rs |
StatsD metrics emission |
src/prometheus.rs |
Prometheus metrics rendering |
src/redis.rs |
Redis connection management |
src/memory/ |
Memory monitoring and resource optimization |
src/telemetry/ |
OpenTelemetry setup |
src/providers/ |
Pluggable provider implementations |
src/traits.rs |
Extension traits (OAuthProvider, WebhookHandler, RouteExtension) |
src/proxy/ |
L7 reverse proxy — WIRED (2026-09-24): built from config.proxy.enabled, layered ahead of routing so it claims only matching paths, and carries Upgrade/WebSocket. No TLS termination (by decision); least-connections falls back to round-robin |
src/l4/ |
L4 TCP proxy — WIRED (2026-09-24): mod l4; ships it, config.l4.enabled turns it on, listeners share the server's shutdown broadcast. Static upstreams per proxy (no catalog needed). UDP still unimplemented and refused per proxy at spawn, not at parse, so one protocol: udp entry cannot stop the config loading |
src/mesh/ |
Circuit breaker + retry backoff — DARK: library-only, and a duplicate of the wired federation/load_shedding.rs |
config/ |
Example YAML config and config documentation |
tests/ |
Integration tests (federation, entity resolution, schema validation) |
load-tests/ |
k6 load test scripts |
module/ |
Nix home-manager and NixOS modules |
nix/ |
Nix build support files |
- substrate -- Nix build patterns (
rust-service-flake.nix,buildRustService) - forge -- CI/CD build platform (Attic cache, GHCR push)
- k8s -- GitOps manifests (FluxCD reconciles Hanabi deployments)
- nexus -- Product monorepo that consumes Hanabi as its BFF
- libraries -- Shared platform libraries (pleme-notifications)