Getting started
Introduction
Introduction #
Husaria is a GraphQL engine written in Go. It introspects a live database catalog, auto-generates a typed GraphQL API for it, enforces row- and column-level permissions sourced from a husaria_data schema, ships an embedded admin console, supports JWT (HS256 / RS256 / JWKS) or admin-secret authentication, serves live and streaming subscriptions over WebSocket, lets operators graft webhook-backed action_<name> fields with custom GraphQL types onto the live schema, bridges external REST APIs as delegated_<source>_<op> fields driven by OpenAPI 3 specs, dispatches webhook events from a transactional queue, and exposes a JSON-RPC 2.0 MCP bridge for LLM tooling on a separate optional port — all without redeploying.
PostgreSQL is the first — and currently the only — database backend. Additional backends are planned. Everything in these docs describes the PostgreSQL backend unless explicitly noted otherwise.
What you get out of the box #
A single static Go binary that, given a Postgres connection string, will:
- Introspect every table in the included schemas (default
public) and publish typed Query / Mutation / Subscription roots. - Enforce permissions read from
husaria_data.table_permissions. Filters are compiled into the generated SQL — a forbidden row never leaves the database. - Serve subscriptions over
graphql-transport-wswith cohort multiplexing: N subscribers running the same query share a single execution per polling tick. - Run an admin console under
/consolewith 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. - Expose ops endpoints — separate
/live(liveness) and/ready(readiness) probes,/version,/metrics(Prometheus), and/healthzaliases. - Forward distributed traces — accepts W3C
traceparent(preferred),X-Trace-ID(legacy), orX-B3-TraceId(Zipkin) on inbound and echoes bothtraceparent+X-Trace-IDon outbound. Synthesises a valid context when no upstream sent one. - Fire row-change events — AFTER-row Postgres triggers
NOTIFYthehusaria_eventschannel; a consumer holdsLISTEN husaria_eventsand auto-enqueues the webhook dispatch. Cron-scheduled events are supported too. See Events. - Merge upstream GraphQL and extra databases — graft other GraphQL endpoints onto the served schema as
<name>_<field>passthrough fields (Remote schemas), and serve tables from more than one Postgres at once, each with its own pool (Sources).
What is intentionally not in the box #
Husaria stays scoped on purpose.
- No central metadata DSL. The configuration document at
/v1/metadatais a serialisation ofhusaria_data.*rows, not a primary source of truth. - No Apollo Federation. Husaria is not a federation gateway and does not speak the Federation subgraph protocol. Remote schemas merge upstream GraphQL root fields by passthrough (each upstream field
fis exposed as<name>_f, returned as JSON) — there is no typed schema stitching. - No third-party session-variable header aliases. Session variables are strictly
X-Husaria-*. The native JWT claims namespace ishttps://husaria.app/jwt/claims.
Architecture at a glance #
HTTP client Postgres
│ ▲
│ POST /v1/graphql │
▼ │
┌──────────────────────────────────────────────┐ │
│ middleware chain │ │
│ admin-secret / JWT → RLS context │ │
│ X-Husaria-* session vars │ │
│ traceparent / X-Trace-ID extraction │ │
└──────────────────────────────────────────────┘ │
│ │
▼ │
┌──────────────────────────────────────────────┐ │
│ graphqlSchemaSlot.Load() (atomic pointer) │ │
│ resolvers walk selection set → │ │
│ compiler.Query → executor │──────┘
│ RLS predicate AND'd into WHERE │
│ relationship filters → EXISTS subqueries │
│ nested array rels → json_agg in LATERAL join │
└──────────────────────────────────────────────┘
The same schema slot is read by the HTTP handler, the subscription runtime, and the embedded console GraphQL endpoint. When a migration applies, an operator hits Reload Schema, or /v1/metadata runs reload_metadata, the slot is rebuilt atomically — the next request sees the new schema without a process restart.
When Husaria fits #
- You already have a Postgres schema and want a typed GraphQL API for it without writing one.
- You want RLS-style permissions that an operator can edit live without redeploying.
- You want a single Go binary you can deploy alongside your existing stack, with a sensible default ops surface (metrics, separate probes, tracing passthrough).
- You're migrating off another GraphQL engine and want wire-shape compatibility (
bigint,uuid,jsonbscalars;_eq/_in/_and/_orfilters; nested-relationship inserts) without rewriting your clients.
Extending the API — picking the right tool #
Husaria ships three ways to talk to external systems. Pick by direction + timing:
| Actions | Delegations | Events | |
|---|---|---|---|
| Triggered by | A GraphQL client calls action_<name> | A GraphQL client calls delegated_<src>_<op> | A row change (NOTIFY) or cron tick |
| Direction | Outbound webhook, on request | Outbound REST, on request | Outbound webhook, on database event |
| GraphQL surface | action_<name> field on Query or Mutation | delegated_<source>_<operation> (Query for GET/HEAD, Mutation for POST/PUT/PATCH/DELETE) | None — pure outbound |
| Response model | Sync (block-and-return) or async (returns a UUID; result polled from action_logs) | Sync (block-and-return) | Fire-and-forget; retries in the background until terminal status |
| Request wire shape | JSON envelope: {action, input, session_variables} as JSON POST | A REST request translated from the OpenAPI spec — path interpolation + query/header/body bucketing | JSON envelope: {trigger_id, op, schema, table, old, new, ts} as JSON POST |
| Retries | Caller-configurable maxRetries (default 0) | None at the delegation layer | Exponential backoff capped at 1 hour, until exhausted |
| RBAC | Per-action row in husaria_data.action_permissions | Per-source row in husaria_data.delegation_permissions (covers every endpoint under that source) | n/a — outbound only, no caller to gate |
| SSRF guard | Shared allowlist | Shared allowlist (opt-out via HUSARIA_DELEGATION_ALLOW_PRIVATE_HOSTS=true) | Shared allowlist (no opt-out) |
| Body signing | None | None | Optional X-Husaria-Signature HMAC-SHA256 when HUSARIA_EVENTS_SIGNING_SECRET is set |
| Transactional with source change | Lives inside the GraphQL request transaction | Lives inside the GraphQL request transaction | No. NOTIFY is best-effort; a consumer outage drops the notification but does not block the source write |
| Storage | husaria_data.actions / action_permissions / action_logs / action_types | husaria_data.delegations / delegation_endpoints / delegation_permissions | public.husaria_event_triggers / husaria_event_invocation_logs |
| Console tab | Actions (with Action Types sub-nav) | Delegations | Events (feature-flagged: requires HUSARIA_ENABLE_EVENTS=true) |
| Use when | You need a single external service called as part of a GraphQL mutation (payments, email, CRM) | You want an entire third-party REST API to show up as GraphQL fields | You need to notify external services when rows change, or fire something on a schedule |
Rule of thumb: request → response on a single endpoint is an action. Whole-REST-API → GraphQL is a delegation. Row change or cron → outbound webhook is an event.
Where to go next #
- Quick start — get a secured Husaria running against a local Postgres in under five minutes. Three install paths (binary, Docker Compose, Helm).
- Configuration — the full environment-variable surface, grouped by area.
- Authentication — OPEN vs SECURED mode and the JWT decision tree.
- Permissions & RLS — how to write a policy and how it lowers into SQL.
- Actions — webhook-backed custom fields, RBAC, sync + async.
- Delegations — bridge a third-party REST API into your GraphQL schema using its OpenAPI spec.
- Events — dispatch webhook calls from a transactional queue, with retries, advisory-lock single-flight, and HMAC body signing.
- Read-only replicas — scale reads with a standby Postgres.
- Tracing — wire Husaria into your existing OpenTelemetry stack.
- MCP for LLM tooling — JSON-RPC 2.0 bridge at
/mcp/*on the optional helper port. Read-only tools expose the schema + actions + delegations to LLM consumers.