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.

PostgreSQL ready More backends planned·docker pull ghcr.io/lukaszraczylo/husaria
localhost:8080/console
# RLS pushed into SQL — this only returns rows the role can seequery MyNotes {notes(where: { archived: { _eq: false } }) { id body updated_at }}
// 2 rows · 18 ms · role=user{"data": {"notes": [ { "id": "4f1a…", "body": "book the flight" }, { "id": "7b22…", "body": "draft slides" } ] }}

// 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

grant_select_own.sql
-- 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

notes.graphql
query Notes {
  notes(where: { archived: { _eq: false } }) {
    id
    body
    updated_at
  }
}

C Husaria compiles A + B into one SQL statement

generated.sql
-- 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@pod-7c4f:~$
$ 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.

MethodPathWhat it does
GET/liveLiveness. 200 once the process is reachable. Use for k8s liveness probes.
GET/readyReadiness. 503 with status:"warming-up" until the initial schema is built. Use for k8s readiness probes.
GET/healthzAlias of /ready; kept for older deployments.
GET/versionBuild info + read_only flag so a load balancer can route around replicas.
GET/metricsPrometheus 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

Husaria console — GraphiQL tab

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.

install.sh
# 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

./husaria

The docs cover quick-start through tracing in twelve pages. The repo carries the binary, the chart, and an honest README.