Reference
Features, end to end.
Capabilities below are in the codebase at HEAD. Anything not listed here is either on the roadmap or a deliberate non-goal — see the Shipped / Roadmap / Not planned columns on the landing page.
// 01
What's actually built.
11 capabilities, each linked to its docs page. No marketing labels — the names match what the engine logs at startup.
- 01
GraphQL from your Postgres catalog
Husaria introspects the live Postgres schema and exposes a typed GraphQL API for it. No metadata files to sync.
- 02
Row- and column-level permissions
Permissions live in a husaria_data schema. Per-role, per-table, per-operation policies with column allow-lists and JSON filters.
- 03
Admin secret or JWT — your choice
Two auth modes. Admin-secret header for service callers, HS256/RS256 JWTs for user sessions. Auth0, Zitadel, Keycloak — all wire up cleanly.
- 04
Live queries & streaming subs
graphql-transport-ws subscriptions over WebSocket. Cohort multiplexing means N subscribers on the same query share one execution per tick.
- 05
Webhook actions
Register custom action_<name> fields that delegate resolution to an external HTTP webhook. Sync or async, RBAC-gated, with case-insensitive header forwarding.
- 06
REST APIs as GraphQL fields
Point Husaria at any OpenAPI 3 spec and tick the endpoints you want — each becomes a delegated_<source>_<op> field with per-source auth, RBAC, and response caching.
- 07
Embedded admin console
Eighteen tabs: GraphQL, Data Browser, SQL Runner, Schema, API Explorer, Permissions, Actions, Delegations, Remote Schemas, Exposed Functions, REST Endpoints, Allow-list, Sources, Events, Migrations, Performance, Metadata, Settings.
- 08
Live runtime settings
Toggle log level, introspection, maintenance mode, and more from the console — most settings apply without restart.
- 09
Read-only replica mode
HUSARIA_READ_ONLY=true points a second instance at a Postgres read replica. Queries + subscriptions pass through; mutations + console writes return 503; the load balancer routes around it.
- 10
One Go binary
Statically linked, CGO_ENABLED=0. Drop it next to your Postgres and run. No JVM, no Node runtime, no sidecar. Distroless multi-arch container available.
- 11
Production-shaped surface
Prometheus metrics, separate /live + /ready probes, traceparent passthrough, configurable timeouts, request size limits, CORS allow-lists — ready for ops day one.
// 02
Auth modes.
One env var picks the chain. Open mode is for local dev and internal-only deployments — the kind of network where every caller is already trusted. Secured mode is the prod posture: admin secret or validated JWT.
| Aspect | Open | Secured |
|---|---|---|
| Trigger | HUSARIA_ADMIN_SECRET unset | HUSARIA_ADMIN_SECRET set |
| Caller default | Everyone is admin | 401 without secret or JWT |
| X-Husaria-Role | Impersonates that role; RLS still enforced | Must appear in JWT claims.allowed_roles |
| JWT honoured | Yes — for role resolution | Required (or admin secret) |
| Wrong secret | — | 401, no JWT fallthrough (attack signal) |
// 03
Environment variables.
.env.example in the repo is the source of truth; the inventory here is the most-used subset, grouped by area. 80 keys total.
Server
25 keys| Variable | Default | Description |
|---|---|---|
HUSARIA_HOST | 0.0.0.0 | Bind address. |
HUSARIA_PORT | 8080 | HTTP port. |
HUSARIA_READ_TIMEOUT | 30s | HTTP read timeout. |
HUSARIA_WRITE_TIMEOUT | 30s | HTTP write timeout. |
HUSARIA_IDLE_TIMEOUT | 120s | Connection idle timeout. |
HUSARIA_SHUTDOWN_TIMEOUT | 30s | Graceful shutdown deadline. |
HUSARIA_MAX_HEADER_BYTES | 1048576 | Max HTTP header bytes. |
HUSARIA_MAX_REQUEST_BYTES | 1048576 | Max body bytes. |
HUSARIA_ENABLE_CONSOLE | false | Mount the embedded /console UI. |
HUSARIA_ENABLE_INTROSPECTION | true | Allow GraphQL introspection. Single non-bypassable gate — the caller-controlled Referer/header backdoor was removed. |
HUSARIA_ALLOW_OPEN_MODE | false | Explicit opt-in to OPEN MODE. When HUSARIA_ADMIN_SECRET is unset, the server refuses to start unless this is =true — open mode (every caller is admin, full RLS bypass) is never the silent default. Intended for local dev only. |
HUSARIA_ALLOWLIST_MODE | disabled | Query allow-list / persisted-query enforcement at boot. disabled (default) resolves and auto-stores APQ hashes without gating; log-only records violations without blocking; enforce rejects any operation whose hash is not on the allow-list. Also settable live via the allowlist_mode runtime setting. |
HUSARIA_CORS_ALLOWED_ORIGINS | unset | Comma-separated CORS allow-list (required in prod). |
HUSARIA_CORS_ALLOWED_HEADERS | Content-Type,Authorization | Allowed inbound request headers. |
HUSARIA_TRUSTED_PROXIES | unset | CIDRs trusted as proxies. The rate limiter checks the TCP RemoteAddr against this list (plus loopback) BEFORE consulting X-Forwarded-For — trusted transports bypass the per-IP cap, untrusted ones can't spoof identity via XFF. |
HUSARIA_MEMORY_LIMIT | unset | Soft memory ceiling (Go's GOMEMLIMIT). Accepts decimal (500M, 2G) and binary (256Mi, 1Gi) suffixes. Falls back to 80% of the cgroup memory.max when readable (v1 + v2 supported); off when neither is set. |
HUSARIA_GOGC | unset | Heap growth percent between GCs. Defaults to 50 when a memory limit is in play, 75 otherwise. Lower = heap stays close to the live set, more CPU on GC. |
HUSARIA_FREE_OS_MEM_INTERVAL | unset | Periodic debug.FreeOSMemory cadence (Go duration). Default 60s when a memory limit is set, 0 (off) otherwise. Returns idle spans to the OS so RSS doesn't drift above the live set on a quiet pod. |
HUSARIA_READ_ONLY | false | Run as a read-only replica. Mutations + console writes return 503; bootstrap of husaria_data is skipped. |
HUSARIA_GIN_DEBUG | false | Force gin into DebugMode (verbose per-route logging). Otherwise ReleaseMode unless HUSARIA_ENV=development. |
HUSARIA_ENV | unset | Three effects. (1) HUSARIA_ENV=development flips gin to DebugMode. (2) HUSARIA_ENV in {production, prod, live} enables error-response redaction. (3) Same values REFUSE startup when HUSARIA_ADMIN_SECRET is empty — prevents booting OPEN MODE in prod. |
ENVIRONMENT | unset | Logging-mode gate. 'production' selects ProductionConfig (Level=info, sampling on). Anything else (including unset) selects DevelopmentConfig (Level=debug). Separate from HUSARIA_ENV. |
HUSARIA_LOG_LEVEL | unset | Initial log level override. Replaces whichever level ENVIRONMENT selected. Valid values: debug, info, warn, error, fatal. |
LOG_LEVEL | unset | Bare log-level override read by the logger at configure time. Same valid values as HUSARIA_LOG_LEVEL. |
HUSARIA_IMPORT_COMPAT | false | Opt-in to reading relationship names from an imported hdb_catalog.hdb_metadata document. Only enable on instances migrated from a prior deployment that stored that document. |
Database
12 keys| Variable | Default | Description |
|---|---|---|
DATABASE_URL | unset | Full DSN — takes priority over discrete vars. When supplied without sslmode, Husaria injects one from HUSARIA_DB_SSL_MODE (default require). |
HUSARIA_DB_HOST | localhost | Postgres host. |
HUSARIA_DB_PORT | 5432 | Postgres port. |
HUSARIA_DB_NAME | husaria | Database name. |
HUSARIA_DB_USER | postgres | DB user. |
HUSARIA_DB_PASSWORD | unset | DB password. |
HUSARIA_DB_SSL_MODE | require | libpq sslmode. verify-ca and verify-full both honour chain verification now (the older bug that turned verify-ca into require has been fixed). Also the value injected into DATABASE_URL when that DSN omits it. |
HUSARIA_DB_MAX_OPEN_CONNS | 25 | Pool: max open connections. |
HUSARIA_DB_MAX_IDLE_CONNS | 25 | Pool: max idle connections. |
HUSARIA_DB_CONN_MAX_LIFETIME | 1h | Pool: max connection lifetime. |
HUSARIA_DB_CONN_MAX_IDLE_TIME | 10m | Pool: max idle time before a connection is closed. |
HUSARIA_DB_QUERY_TIMEOUT | 30s | Per-query execution timeout. |
Authentication
23 keys| Variable | Default | Description |
|---|---|---|
HUSARIA_ADMIN_SECRET | unset | Required to enter SECURED mode. Unset = OPEN MODE. Refused at startup when empty + HUSARIA_ENV ∈ {production, prod, live}. Header comparison is constant-time; both X-Husaria-Admin-Secret and X-Admin-Secret accepted. |
HUSARIA_ADMIN_ENABLE | true | Mount the admin API surface. |
HUSARIA_ADMIN_PATH | /admin | Admin API path prefix. |
HUSARIA_ADMIN_ALLOWED_IPS | 127.0.0.1,::1 | IP allow-list for admin endpoints. |
HUSARIA_JWT_SECRET | unset | HS256 signing key. Minimum 32 bytes (RFC 7518 §3.2; enforced at JWTManager construction). |
HUSARIA_JWT_PUBLIC_KEY_PATH | unset | RS256 verify key (PEM). Misconfig is fatal. |
HUSARIA_JWT_PRIVATE_KEY_PATH | unset | RS256 sign key (PEM). |
HUSARIA_JWT_JWKS_URL | unset | RS256 — JWKS endpoint (RFC 7517). Wins over HUSARIA_JWT_PUBLIC_KEY_PATH if both are set. Lazy fetch, cached, refreshed on kid miss. Singleflight + 5s debounce + stale-but-present fallback. |
HUSARIA_JWT_JWKS_CACHE_TTL | 1h | JWKS cache TTL. Go duration (15m, 1h, 24h). Invalid value is fatal at startup. |
HUSARIA_JWT_ISSUER | unset | Required JWT 'iss' claim. |
HUSARIA_JWT_AUDIENCE | unset | Comma-separated allowed audiences. |
HUSARIA_JWT_ALGORITHM | HS256 | Local-issuer algorithm. |
HUSARIA_JWT_EXPIRY | 24h | Local-issuer access token expiry. |
HUSARIA_JWT_REFRESH_EXPIRY | 168h | Local-issuer refresh token expiry. |
HUSARIA_UNAUTHORIZED_ROLE | anonymous | Role used for unauthenticated requests. |
HUSARIA_DEFAULT_ROLE | user | Default role applied when claims lack one. |
HUSARIA_AUTH_ALLOW_PRIVATE_HOSTS | false | SSRF opt-out for the auth subsystem. Without it, JWKS fetches and auth/login webhooks refuse to connect to loopback / RFC1918 / link-local / CGNAT / IMDS hosts. Set =true only if your identity provider legitimately lives on a private network. |
HUSARIA_AUTH_WEBHOOK_URL | unset | Auth webhook endpoint. When set, inbound requests are authenticated by POSTing to this URL. |
HUSARIA_AUTH_WEBHOOK_TIMEOUT | 10s | Per-call timeout for the auth webhook. |
HUSARIA_AUTH_WEBHOOK_RETRIES | 3 | Retry attempts for a failed auth webhook call. |
HUSARIA_SESSION_COOKIE_NAME | husaria-session | Name of the session cookie. |
HUSARIA_SESSION_COOKIE_SECURE | true | Set the Secure flag on the session cookie (HTTPS-only). |
HUSARIA_SESSION_COOKIE_DOMAIN | unset | Domain scope for the session cookie. The config validator suggests setting it for better security. |
Subscriptions
5 keys| Variable | Default | Description |
|---|---|---|
HUSARIA_WEBSOCKET_ENABLE | true | Enable WebSocket subscriptions. |
HUSARIA_WEBSOCKET_PORT | 8081 | WebSocket port if distinct from HTTP (dedicated-port branch is a stub today; mainline mount is on the shared HTTP port). |
HUSARIA_WEBSOCKET_PATH | /v1/graphql/ws | Subscription endpoint path. |
HUSARIA_LIVE_QUERIES_REFETCH_INTERVAL_MS | 1000 | Live-query poll interval. |
HUSARIA_GRAPHQL_CONNECTION_INIT_WAIT_TIMEOUT | 3s | graphql-transport-ws init timeout. |
Cache
11 keys| Variable | Default | Description |
|---|---|---|
REDIS_HOST | localhost | Redis host. Falls back to in-memory if unreachable. |
REDIS_PORT | 6379 | Redis port. |
REDIS_PASSWORD | unset | Redis password. |
DISABLE_CACHE | false | Disable the response-cache middleware entirely. |
HUSARIA_REDIS_POOL_SIZE | 10 | Redis connection pool size. |
HUSARIA_REDIS_MIN_IDLE_CONNS | 2 | Minimum idle connections kept in the Redis pool. |
HUSARIA_REDIS_MAX_IDLE_CONNS | 5 | Maximum idle connections kept in the Redis pool. |
HUSARIA_REDIS_DIAL_TIMEOUT | 5s | Timeout for establishing a Redis connection. |
HUSARIA_REDIS_READ_TIMEOUT | 3s | Redis socket read timeout. |
HUSARIA_REDIS_WRITE_TIMEOUT | 3s | Redis socket write timeout. |
HUSARIA_REDIS_POOL_TIMEOUT | 4s | Time to wait for a free connection from the Redis pool. |
Migrations
2 keys| Variable | Default | Description |
|---|---|---|
HUSARIA_AUTO_MIGRATE | false | Apply file-based migrations at startup. No-op on read-only replicas. Cannot reach migrations/samples/. |
HUSARIA_MIGRATIONS_PATH | ./migrations | Path to migration files. Loader does not recurse. |
Metrics
2 keys| Variable | Default | Description |
|---|---|---|
HUSARIA_METRICS_ENABLE | true | Expose Prometheus metrics. Endpoint is on adminGroup — Prometheus needs X-Husaria-Admin-Secret in its scrape config. |
HUSARIA_METRICS_PATH | /metrics | Prometheus exposition path. |
import { createClient } from 'graphql-ws'
const client = createClient({
url: 'ws://localhost:8080/v1/graphql/ws',
connectionParams: {
headers: {
'X-Husaria-Admin-Secret': process.env.HUSARIA_ADMIN_SECRET!
}
}
})
// One subscriber. Behind the scenes Husaria
// joins this into a cohort with anyone else
// running the exact same query — one Postgres
// poll per tick covers them all.
const unsubscribe = client.subscribe(
{ query: 'subscription { notes_stream(cursor: { initial_value: { id: 0 }, ordering: ASC }, batch_size: 50) { id body } }' },
{ next: (msg) => console.log(msg), error: console.error, complete: () => {} }
)// 04
Subscriptions.
graphql-transport-ws over WebSocket. The runtime polls the database on a configurable interval (default 1000 ms), hashes the canonical-JSON result, and emits next only when the hash changes.
Identical queries from N subscribers share one execution per tick. The cohort key is SHA-256(query + vars + role + admin-bypass) — adding a sixth subscriber costs nothing extra.
- Poll interval
- 1000 ms (default)
- Cohort key
- SHA-256, versioned
- Append-only feeds
- _stream variant
- Metrics
- husaria_subs_*