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-ws with cohort multiplexing: N subscribers running the same query share a single execution per polling tick.
  • Run an admin console under /console with 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 /healthz aliases.
  • Forward distributed traces — accepts W3C traceparent (preferred), X-Trace-ID (legacy), or X-B3-TraceId (Zipkin) on inbound and echoes both traceparent + X-Trace-ID on outbound. Synthesises a valid context when no upstream sent one.
  • Fire row-change events — AFTER-row Postgres triggers NOTIFY the husaria_events channel; a consumer holds LISTEN husaria_events and 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/metadata is a serialisation of husaria_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 f is 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 is https://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, jsonb scalars; _eq / _in / _and / _or filters; 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:

ActionsDelegationsEvents
Triggered byA GraphQL client calls action_<name>A GraphQL client calls delegated_<src>_<op>A row change (NOTIFY) or cron tick
DirectionOutbound webhook, on requestOutbound REST, on requestOutbound webhook, on database event
GraphQL surfaceaction_<name> field on Query or Mutationdelegated_<source>_<operation> (Query for GET/HEAD, Mutation for POST/PUT/PATCH/DELETE)None — pure outbound
Response modelSync (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 shapeJSON envelope: {action, input, session_variables} as JSON POSTA REST request translated from the OpenAPI spec — path interpolation + query/header/body bucketingJSON envelope: {trigger_id, op, schema, table, old, new, ts} as JSON POST
RetriesCaller-configurable maxRetries (default 0)None at the delegation layerExponential backoff capped at 1 hour, until exhausted
RBACPer-action row in husaria_data.action_permissionsPer-source row in husaria_data.delegation_permissions (covers every endpoint under that source)n/a — outbound only, no caller to gate
SSRF guardShared allowlistShared allowlist (opt-out via HUSARIA_DELEGATION_ALLOW_PRIVATE_HOSTS=true)Shared allowlist (no opt-out)
Body signingNoneNoneOptional X-Husaria-Signature HMAC-SHA256 when HUSARIA_EVENTS_SIGNING_SECRET is set
Transactional with source changeLives inside the GraphQL request transactionLives inside the GraphQL request transactionNo. NOTIFY is best-effort; a consumer outage drops the notification but does not block the source write
Storagehusaria_data.actions / action_permissions / action_logs / action_typeshusaria_data.delegations / delegation_endpoints / delegation_permissionspublic.husaria_event_triggers / husaria_event_invocation_logs
Console tabActions (with Action Types sub-nav)DelegationsEvents (feature-flagged: requires HUSARIA_ENABLE_EVENTS=true)
Use whenYou 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 fieldsYou 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.