Open source · MIT
GraphQL APIs for your database, in seconds.
Husaria introspects a live database catalog and serves a typed GraphQL API for it. Permissions live as plain rows. No metadata files. One Go binary you can drop next to Postgres and run.
docker pull ghcr.io/lukaszraczylo/husaria// 01
One request, end to end.
Husaria's permission model is a Postgres table, not a DSL. A policy is a row. The role on the request is matched against husaria_data.table_permissions at compile time. The filter and the column allow-list are folded directly into the generated SQL. There is no post-filter — a forbidden row never leaves the database.
Three things below: the policy, the GraphQL query a client sends, and the SQL Husaria emits. Read them in order.
A Operator writes the policy once
-- Allow the 'user' role to SELECT their own notes,
-- but only the id and body columns.
INSERT INTO husaria_data.table_permissions
(role_id, table_schema, table_name, operation, columns, filter)
VALUES
((SELECT id FROM husaria_data.roles WHERE name = 'user'),
'public', 'notes', 'select',
'["id","body"]'::jsonb,
'{"user_id": {"_eq": "X-Husaria-User-Id"}}'::jsonb);B Client asks for what it wants
query Notes {
notes(where: { archived: { _eq: false } }) {
id
body
updated_at
}
}C Husaria compiles A + B into one SQL statement
-- Husaria compiles the role's policy into the WHERE clause:
SELECT n.id, n.body, n.updated_at
FROM public.notes n
WHERE n.user_id = $1 -- from X-Husaria-User-Id
AND n.archived = false -- from the caller
LIMIT 100; -- HUSARIA_GRAPHQL_DEFAULT_LIMIT$ HUSARIA_ADMIN_SECRET=... HUSARIA_ENABLE_CONSOLE=true ./build/husaria
[INFO] husaria v0.1.0 PID=1 pgo=on
[INFO] Connected to PostgreSQL database
[INFO] husaria_data schema bootstrap: ok
[INFO] runtime settings store loaded: 7 keys
[INFO] schema introspection: 47 tables, 312 columns, 89 foreign keys
[INFO] rls registry: 12 roles, 3 inheritance edges, 89 policies
[INFO] action extensions: 2 fields registered (1 query, 1 mutation)
[INFO] delegation extensions: 6 fields registered (4 query, 2 mutation)
[INFO] console mounted at /console (read-only=false)
[INFO] GraphQL ready POST /v1/graphql
[INFO] subscriptions ready GET /v1/graphql/ws
[INFO] metrics, probes ready GET /metrics, /live, /ready, /version
[INFO] http listener bound 0.0.0.0:8080
// 02
Boot it, watch it land.
A clean pod boot is six log lines from process start to "GraphQL ready". The schema introspection is the slow step; everything else is local work. Once /ready returns 200 the engine is live — your load balancer can admit traffic immediately.
Replica boots skip the bootstrap step (the standby's catalog is already replicated via WAL); the action and delegation extensions register zero fields when you haven't configured any. The unhappy paths are uneventful.
// 03
Scope, on purpose.
A short, honest read of where we are. What's in the binary today, what's coming next, and what we've decided not to build. The third column is the most useful: knowing what a tool will never become tells you whether to adopt it.
Shipped
- GraphQL CRUD
- introspected from your tables; insert / update / delete / select / aggregate roots; nested object- and array-relationship inserts in one transaction
- RLS as data
- rows in husaria_data.table_permissions; filters compile into SQL
- Subscriptions
- graphql-transport-ws with cohort multiplexing on identical queries
- Auth
- admin secret OR JWT (HS256/RS256, with lazy JWKS fetch for RS256); optional iss/aud gates
- Webhook actions
- register a field, point at a URL; sync or async; RBAC at row level; custom output types via the Action Types catalogue
- REST delegations
- feed any OpenAPI 3 spec; pick endpoints; they show up as GraphQL fields
- Webhook events (end-to-end)
- NOTIFY producer + cron scheduler + queue drainer. Row change or cron tick → invocation row → delivery with advisory-lock single-flight, retry, SSRF guard, HMAC signing.
- Read replicas
- HUSARIA_READ_ONLY=true; mutations + console writes 503; bootstrap skipped
- Hot schema reload
- apply migration / reload metadata / hit /schema/reload — no restart
- Embedded console
- thirteen tabs at /console; gated by admin secret in secured mode
- MCP for LLMs
- optional JSON-RPC 2.0 bridge on a separate helper port; read-only schema / actions / delegations introspection for cluster-internal agents
- Production surface
- separate /live + /ready probes, /metrics, traceparent passthrough
Roadmap
- Additional database backends
- PostgreSQL is the first and currently the only backend. The schemagen and execution layers are written against a backend interface; more drivers are planned.
- Invocation log surfacing
- the events pipeline writes per-attempt rows to husaria_event_invocation_logs, but the console Events tab currently lists triggers only — the per-trigger history view is the next iteration.
- Per-event Prometheus series
- general-purpose husaria_* metrics cover events traffic today; dedicated husaria_events_* series are on the roadmap.
On the list, not yet in the binary. Track these in the GitHub issues if you need a date.
Not planned
- Metadata DSL
- configuration is rows in husaria_data, not a YAML/JSON file you check in. The /v1/metadata document is a serialisation of those rows, not a primary source of truth.
- GraphQL federation
- one engine, one schema, one database. REST delegations bridge external APIs; no remote-schema stitching planned.
- Third-party header aliases
- session variables are strictly X-Husaria-*. The legacy JWT namespace is read as a fallback only — no plans to broaden the aliasing surface.
- Private-network webhook gateway
- the SSRF allowlist on actions / delegations / events refuses loopback / RFC1918 / link-local / CGNAT / non-http(s). Layer a public proxy if you need to reach a private receiver.
Deliberate non-goals — not a backlog. Each one is a "no" for a reason.
// 04
Wired into your ops day one.
Separate liveness and readiness — because they answer different questions and k8s wants to ask both. Prometheus metrics on the default registry. traceparent in, traceparent out — Husaria propagates W3C trace context unconditionally, no env var to flip.
The point is that none of this needs configuration. The defaults are the right defaults.
| Method | Path | What it does |
|---|---|---|
| GET | /live | Liveness. 200 once the process is reachable. Use for k8s liveness probes. |
| GET | /ready | Readiness. 503 with status:"warming-up" until the initial schema is built. Use for k8s readiness probes. |
| GET | /healthz | Alias of /ready; kept for older deployments. |
| GET | /version | Build info + read_only flag so a load balancer can route around replicas. |
| GET | /metrics | Prometheus exposition. husaria_executor_*, husaria_subs_*, husaria_cache_*. |
// 05
What the operator sees.
Fourteen panels, one binary. Pick a feature on the left to see the actual screen the operator gets when they open /console.
Husaria · /console · GraphiQL

Custom GraphiQL with one-click execution against the live schema — query/mutation over HTTP, subscriptions over WebSocket.
Read the GraphiQL docs// 06
Install.
Same binary, four delivery paths. Pre-built release archive, Docker Compose, Helm chart, or build from source. All four accept the same env vars and reach the same /v1/graphql.
# download the latest pre-built release for your OS / arch
# (Linux + macOS, amd64 + arm64; Windows via .zip on the releases page)
TAG=$(curl -fsSL https://api.github.com/repos/lukaszraczylo/husaria/releases/latest \
| sed -n 's/.*"tag_name": "\([^"]*\)".*/\1/p')
curl -fsSL "https://github.com/lukaszraczylo/husaria/releases/download/${TAG}/husaria_${TAG#v}_$(uname -s)_$(uname -m).tar.gz" | tar xz
# minimum env to start
export HUSARIA_DB_HOST=localhost
export HUSARIA_DB_USER=husaria
export HUSARIA_DB_PASSWORD=changeme
export HUSARIA_ADMIN_SECRET=admin-secret-key-change-me
export HUSARIA_ENABLE_CONSOLE=true
./husariaThe docs cover quick-start through tracing in twelve pages. The repo carries the binary, the chart, and an honest README.