# compare
gqlith vs the alternatives
We picked this fight on purpose. Every tool below is good at something. None covers the ground we wanted to stand on — GraphQL + REST + MCP + a typed client SDK from one schema, one permission model across all of them, generated as code you own. Compared within category (codegen vs runtime engine vs router), with the wording fact-checked against each competitor’s docs.
- Codegen: gqlith, Prisma + Pothos, ZenStack. You own the generated code; there is no vendor process at runtime.
- Runtime engine: Hasura, PostGraphile, PostgREST, Exograph. A vendor binary, sidecar or Postgres extension is a permanent runtime dependency.
- Federation router: Cosmo. Composes other GraphQL services into a supergraph — a different category we list for clarity, not parity.
# overview
The whole field, one matrix
Twenty-one capabilities across every tool we compare to. gqlith’s column is the shape we set out to build — four surfaces from one schema, with the governance generated in. Read down a column for a tool’s footprint; read across a row to see who ships what.
- ✓ Built-in
- ~ Partial · add-on · via RLS
- ✗ Not offered
| gqlith | Hasura | PostGraphile | pg_graphql | PostgREST | Exograph | Cosmo | Prisma + Pothos | ZenStack | |
|---|---|---|---|---|---|---|---|---|---|
| GraphQL API | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ~ | ✓ | ✗ |
| REST API (JSON:API) | ✓ | ~ | ✗ | ✗ | ~ | ✗ | ~ | ✗ | ✓ |
| MCP server, governed | ✓ | ~ | ✗ | ✗ | ~ | ✓ | ✓ | ✗ | ✗ |
| Typed client SDK, generated | ✓ | ✗ | ✗ | ✗ | ~ | ✗ | ~ | ~ | ✓ |
| One permission model, all surfaces | ✓ | ~ | ✗ | ✗ | ✗ | ~ | ✗ | ✗ | ~ |
| One filter language, all surfaces | ✓ | ~ | ✗ | ✗ | ✗ | ~ | ✗ | ✗ | ✗ |
| Row-level authorization | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ~ | ✓ |
| Generated Postgres RLS policies | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Multi-tenancy, built-in | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Tenant-defined custom fields | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Typed, validated JSONB | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ~ |
| File uploads & storage | ✓ | ~ | ✗ | ✗ | ✗ | ✗ | ~ | ✗ | ✗ |
| Durable webhooks (outbox) | ✓ | ✓ | ~ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Scheduled jobs (crons) | ✓ | ✓ | ~ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Optimistic concurrency, built-in | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| In-transaction audit log | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Cross-mutation transactions | ✓ | ✓ | ~ | ~ | ✗ | ✓ | ✗ | ~ | ~ |
| Full nested mutations | ✓ | ~ | ~ | ✗ | ✗ | ~ | ✗ | ~ | ~ |
| Subscriptions, per-row authz | ✓ | ✓ | ~ | ✗ | ✗ | ✗ | ~ | ✗ | ✗ |
| Rate limiting, built-in & free | ✓ | ~ | ~ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ |
| No vendor runtime (own the code) | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ |
Compared within category, graded strictly — ~ marks a real but narrower, paid, preview, or add-on path. Row definitions: “one permission model / one filter language” means one data-authorization model (or filter grammar) spanning GraphQL-, REST- and MCP-class surfaces — a single-surface tool scores ✗, covering part of the spread scores ~. “Cross-mutation transactions” means multiple root mutations in one API request, atomic — an in-process ORM $transaction scores ~. “No vendor runtime” means the product emits code you own rather than executing your API inside a vendor process or extension (pg_graphql runs inside Postgres, but it is still vendor code executing at runtime). Every claim is grounded in each tool’s own docs.
# runtime engine
gqlith vs Hasura
License v2 core Apache-2.0 (LTS, EOL Jan 2027); v3 DDN engine Apache-2.0 — CLI, console and control plane proprietary
The 800-lb gorilla, mid-pivot: DDN is maintained, but the company’s headline is now PromptQL.
Why gqlithAs a Hasura alternative, gqlith keeps config in the database, not a cloud control plane — and ships the JSON:API REST surface, native MCP, webhooks and crons that DDN leaves at plugin, alpha or unsupported.
The full breakdown — what they do well, where we differ
Hasura is still the default reach in this space, but the ground has shifted: the company pivoted its headline to PromptQL (June 2025), v2 is on LTS with EOL in January 2027, and DDN’s own feature matrix lists REST as engine-plugin-only, event triggers as work-in-progress and cron triggers as unsupported. There is no native MCP endpoint — the official MCP offering is an alpha shim around the separate PromptQL product. Authorization is Hasura’s own layer, which by its own docs replaces Postgres RLS rather than using it. And while the DDN engine and connectors are Apache-2.0, metadata builds require the proprietary cloud control plane — there is no free, open path to compile metadata on your own infrastructure.
| Capability | gqlith | Hasura |
|---|---|---|
| Surfaces from one schema | GraphQL, JSON:API 1.1 REST, MCP, typed TS client SDK | GraphQL; DDN REST via engine plugin (not JSON:API); no native MCP — PromptQL MCP shim (alpha) is a separate product |
| Permission model spread | One model — RBAC, row filters, tenancy — enforced identically on all surfaces, extended into Postgres RLS and the generated client | One model across GraphQL and its saved-query REST endpoints (PromptQL inherits it) — but it stops at the engine: no RLS generation, no generated client, external metadata store with documented drift risk |
| Database-level security | Generates Postgres RLS policies from the same annotations (opt-in), claims propagated per transaction | Explicitly replaces Postgres RLS with its own authz layer |
| Filter language | One filter language — object, text DSL, agent string — injection-safe | GraphQL-only `where` dialect |
| Multi-tenancy | Built-in row mode, no leakage, automatic across protocols | DIY — session variables + permission rules |
| Nested mutations | create, connect, connectOrCreate, update, upsert, disconnect, delete, replace, set — multi-hop, generated | Insert-only nesting in v2; nested UPDATE unsupported (#1573) |
| Subscription authorization | Per-row at event time, with the caller's claims re-resolved per event | Row filters re-run per poll/event — but with session variables frozen at connection time; a revoked role streams until the socket closes |
| Optimistic concurrency | Built-in (`_version`, ETag / If-Match) | Open feature request (#6412) |
| Audit log | In-transaction before/after diff, declarative | DIY (Postgres triggers or middleware) |
| Webhooks & scheduled jobs | Durable transactional-outbox delivery, signed webhooks, generated crons — portable transports (Cloudflare, SQS, plain Postgres) | v2: free event triggers (atomic capture, at-least-once) + cron triggers — on the version that reaches EOL Jan 2027; DDN: event triggers WIP, cron unsupported |
| File storage | Governed upload pipeline built-in — mint/confirm, signed URLs, quotas, GC | S3 plumbing via the ndc-storage connector; no governed lifecycle |
| Rate limiting | Built into generated runtime, shared across GraphQL / REST / MCP | v2 CE: none. EE/Cloud: GraphQL-only, needs Redis. DDN: a self-hosted Redis sidecar plugin |
| Typed client SDK | Generated with the API — fluent, selection-typed | BYO graphql-codegen |
| Vendor runtime | None — no engine to operate, own the TypeScript, deploy to Workers / Deno | Permanent runtime engine; DDN metadata builds require the proprietary control plane |
| Where config lives | SQL comments in the database | External metadata store; DDN build/control-plane proprietary |
# runtime library
gqlith vs PostGraphile
License MIT core (v5 GA March 2026)
A polished Node-hosted GraphQL API over Postgres with native RLS authorization — v5 went GA in March 2026.
Why gqlithThe same SQL-comment depth, plus JSON:API REST, MCP and a typed client SDK — generated as code you own, with RLS policies gqlith writes for you instead of policies you write by hand.
The full breakdown — what they do well, where we differ
PostGraphile is what we admire most among the runtime libraries, and v5 (GA March 2026, on the Grafast planning engine) is a real step: subscriptions are now built in and free, and `graphile-export` can export the server schema as executable code. Its authorization philosophy is the opposite of Hasura’s — it respects your hand-written Postgres RLS policies via per-request role switching, enforced for any client that touches the database. It never writes those policies, though: RLS remains something you author and maintain by hand. gqlith compiles the same `@gqlith.*` annotations to both app-layer filters and (opt-in) generated `CREATE POLICY` DDL, so the database enforces the model without a second, hand-maintained policy layer. PostGraphile remains GraphQL-only — no REST, no MCP, no typed client — file uploads are an unported v4 community plugin, background jobs and crons come from the first-party Graphile Worker you wire up separately, and it runs as a Node server process.
| Capability | gqlith | PostGraphile |
|---|---|---|
| Surfaces from one schema | GraphQL, JSON:API 1.1 REST, MCP, typed TS client SDK | GraphQL only |
| Row authorization | App-layer `@gqlith.rowFilter` predicates + opt-in generated Postgres RLS from the same annotations | Native Postgres RLS — respects the policies you write by hand; never generates them |
| Relay Global Object Identification | Cursor connections fully satisfied; no `nodeId` / `node(id)` yet | Full Relay GOI |
| Aggregations | count, sum, avg, min, max, distinctCount — plus groupBy and HAVING | Via the aggregates plugin |
| Cross-mutation transactions | Automatic — one tx for all root mutations, all-or-nothing | v5 creates transactions on demand; root mutations default to separate transactions |
| Subscriptions | SSE, per-row authorization derived from annotations, re-checked per event | Built-in in v5 (LISTEN/NOTIFY) — re-evaluation model, not annotation-derived per-event row authz |
| File uploads | Governed pipeline built-in — mint/confirm, signed URLs, variants, GC | v4 community upload plugin, unported to v5 |
| Typed client SDK | Generated fluent TS client, selection-typed | None — `graphile-export` exports the server schema, not a client |
| MCP | Self-describing, permission-projected, four governed tools | None |
| Vendor runtime | None — no engine to operate, own the TypeScript, deploy to the edge | A Node.js server process |
| Config location | SQL comments — RBAC, row filter, tenancy, OCC, audit, presets, validation, rename, rate limit, files, custom fields, JSONB shapes | SQL comments cover a smaller surface; some config in JS/CLI |
# PG extension
gqlith vs pg_graphql
License Apache-2.0
Supabase’s GraphQL resolver, executed inside Postgres as an extension.
Why gqlithEverything pg_graphql leaves outside its narrow surface — REST, MCP, subscriptions, nested mutations, tenancy, a typed client — without giving up the in-database, SQL-comment config model.
The full breakdown — what they do well, where we differ
pg_graphql resolves GraphQL right inside Postgres — small, fast, and as native to the database as it gets. It depends entirely on Postgres RLS for authorization, which is a strength and a constraint. It has no subscriptions, no nested mutations (issue #294), no REST, and no MCP. Its SQL-comment configuration is limited to naming, pagination and aggregate toggles — not RBAC, tenancy, OCC, audit or validation. Worth knowing the platform context too: Supabase has been walking back instant-API defaults (new tables are no longer auto-exposed as of April 2026, and GraphQL-off-by-default is planned), and Supabase’s typegen emits types for a generic query builder rather than a generated SDK. We compare here so the trade-off is visible: pg_graphql is a great fit when everything you need fits its narrow surface.
| Capability | gqlith | pg_graphql |
|---|---|---|
| Surfaces from one schema | GraphQL, JSON:API 1.1 REST, MCP, typed TS client SDK | GraphQL only |
| Authorization model | RBAC + row filters + tenancy + field visibility in SQL comments — with generated RLS as the opt-in database layer | RLS only, hand-written |
| Nested mutations | Full operator set, multi-hop, generated | None (issue #294) |
| Subscriptions | SSE with per-row authorization at event time | None |
| SQL-comment config scope | RBAC, rowFilter, tenancy, OCC, audit, validation, presets, rename, rate limit, files, custom fields, JSONB shapes | Naming, pagination, aggregate toggles |
| Optimistic concurrency / audit / programmatic tokens | Built-in | DIY |
# runtime engine
gqlith vs PostgREST
License MIT
A REST API auto-generated from your Postgres schema. Authorization via RLS + JWT-to-`SET ROLE`.
Why gqlithThe same clean REST, plus GraphQL, MCP and a typed client from the one schema — and multi-resource atomic writes, which PostgREST’s one-request-one-transaction model can’t do.
The full breakdown — what they do well, where we differ
PostgREST (v14 as of late 2025) is the cleanest standalone REST-from-Postgres option, with a thoughtful filter syntax and a clear authorization model (RLS plus JWT-derived `SET ROLE`). It is REST-only — no GraphQL, no MCP, no subscriptions, no storage — and one request equals one resource transaction, so multi-resource atomic writes are not on the menu. It is deliberately a narrow engine; the layer gqlith generates above it — typed clients, files, webhooks, crons, tenancy, audit — simply isn’t its job.
| Capability | gqlith | PostgREST |
|---|---|---|
| Surfaces from one schema | GraphQL, JSON:API 1.1 REST, MCP, typed TS client SDK | REST only |
| REST media type | JSON:API 1.1 with sparse fieldsets, compound include, ETag/If-Match | Custom REST conventions |
| Cross-mutation transactions | Automatic — all root mutations in one tx | One request = one resource transaction |
| Subscriptions | SSE with per-row authorization | None |
| MCP | Self-describing, permission-projected, four governed tools | Official @supabase/mcp-server-postgrest proxy (works standalone) — thin, single-credential, no permission-projected discovery |
| Files, webhooks, crons | Generated and governed — storage pipeline, transactional-outbox delivery, native crons | None — deliberately narrow engine |
| Authorization | RBAC + row filters + tenancy in SQL comments, identical across surfaces — with generated RLS as the database layer | RLS (hand-written) + JWT-to-`SET ROLE` |
# runtime engine
gqlith vs Exograph
License BSL-1.1 (→ Apache-2.0 Feb 2028); prohibits BaaS use
Closest in feature shape: GraphQL + MCP from a declarative `.exo` DSL.
Why gqlithThe surfaces Exograph is still building — JSON:API REST, a typed client SDK, storage, webhooks — under an Apache-2.0 generator with no BaaS restriction on what you ship.
The full breakdown — what they do well, where we differ
Exograph is the closest match in shape for what we are building, and we both landed on the same conclusion about agents: let them write GraphQL against a governed endpoint instead of juggling dozens of rigid tools. The differences are in depth and breadth. gqlith’s MCP surface is self-describing and permission-projected — `describe_schema` and `filter_help` hand the agent a guide covering only what the caller may see, with runnable examples, and reads are split from writes at the wire. Exograph’s REST surface has been "coming soon" since 2025; there is no typed client SDK, no storage pipeline, and nested mutations support create/update/delete only. It ships things we don’t have yet — an access-controlled `Vector` type for similarity search and enforced persisted operations. The BSL license also prohibits BaaS-style hosting until Feb 2028.
| Capability | gqlith | Exograph |
|---|---|---|
| REST surface | JSON:API 1.1, shipping | Roadmap |
| MCP surface | Self-describing: permission-projected schema guide + filter help, read/write split at the wire, connection-accurate complexity | GraphQL tools with schema declaration; access rules enforced |
| Typed client SDK | Generated fluent TS client, selection-typed | None |
| Nested mutations | create, connect, connectOrCreate, update, upsert, disconnect, delete, replace, set | create / update / delete |
| Files, webhooks, crons | Generated — storage pipeline, durable delivery, native crons | None |
| License | Apache-2.0 (generator); generated code is yours to ship | BSL-1.1 until Feb 2028; prohibits BaaS use |
| Vector similarity | Not yet (tracked) | Built-in `Vector` type with access-controlled `similar` |
| Persisted operations | Not yet (planned) | Enforced in production |
# federation router
gqlith vs WunderGraph Cosmo
License Apache-2.0
An Apollo-Federation router for composing subgraphs into a supergraph.
Why gqlithA different category: gqlith generates the API Cosmo would route to — with subscription authorization and MCP scope derived from annotations, not hand-curated per operation.
The full breakdown — what they do well, where we differ
Cosmo is in a different category — a federation router, not a database-introspection codegen — and we list it for clarity rather than parity. It is genuinely well-equipped at its own layer: safelisted `.graphql` operation files become MCP tools with per-tool JWT scopes from `@requiresScopes`, ConnectRPC turns named operations into HTTP/JSON endpoints with typed SDKs, and GCRA rate limiting ships free in the Apache-2.0 router. The structural difference is where everything comes from: every Cosmo tool, endpoint and SDK is an operation someone hand-curates over APIs that already exist, and data authorization lives in each subgraph. gqlith derives the whole surface — tools, filters, subscription authorization — from the database schema and the caller’s permissions. If you already federate subgraphs, Cosmo is a sensible router and gqlith could be one subgraph behind it.
| Capability | gqlith | WunderGraph Cosmo |
|---|---|---|
| Category | Codegen — generates an API from a Postgres schema | Federation router — composes existing GraphQL subgraphs |
| Subscription authorization | Per-row, automatic, derived from `@gqlith.rowFilter` + tenancy | Per-event filtering via hand-authored `@openfed__subscriptionFilter` directives / Streams handlers |
| Rate limiting | Built into generated runtime, free, shared across GraphQL / REST / MCP | GCRA limiter in the Apache-2.0 router (needs Redis); per-client limits still an open request |
| MCP | Derived from the schema — self-describing, permission-projected | Safelisted operation files as tools, per-tool JWT scopes — hand-curated |
| Audit log | Data mutation log with before/after diff, in-transaction | Schema / governance audit log (not data mutation) |
# codegen
gqlith vs Prisma + Pothos
License Apache-2.0 (ORM); platform features hosted-only
The classic own-the-code stack: Prisma’s typed ORM plus a hand-wired Pothos GraphQL layer.
Why gqlithEverything you’d hand-wire on Prisma + Pothos — REST, MCP, row-level authz, tenancy, OCC, audit, realtime, files — generated for you instead of assembled by hand.
The full breakdown — what they do well, where we differ
Prisma + Pothos is the comparison we take most seriously — same category, same philosophy of owning the code. Prisma’s ORM (v7, now Rust-free) matches our nested-write operator depth, and its in-process client is genuinely well-typed — but it is an ORM, not an API: there is no generated HTTP data surface, so every GraphQL operation is hand-exposed through Pothos, and REST or MCP are yours to build. The platform pivot matters too: Pulse (realtime CDC) was discontinued in March 2025 with no shipped successor, the hosted MCP server is admin-grade and Prisma-Postgres-only, and native RLS support has been an open issue since 2022 (the documented pattern is a session-variable workaround). If assembling that stack by hand is what you want, you can absolutely get there; gqlith is what we want when it should be generated.
| Capability | gqlith | Prisma + Pothos |
|---|---|---|
| Surfaces from one schema | GraphQL, JSON:API 1.1 REST, MCP, typed TS client SDK — all generated | GraphQL via Pothos (you wire each operation). No generated REST or MCP data API |
| Typed client | Generated fluent SDK over HTTP — usable from any frontend | Prisma Client is typed but in-process — it is the server’s ORM, not a client SDK |
| Authorization breadth | RBAC + row filters + tenancy + field visibility in SQL comments — plus generated Postgres RLS (opt-in) | `@pothos/plugin-scope-auth` for field/type/operation RBAC incl. per-object scopes; query-level row filtering, RLS (open issue since 2022), tenancy, OCC and audit all DIY |
| Realtime | SSE subscriptions, per-row authorized at event time | Pulse discontinued March 2025; no shipped successor |
| Cross-mutation transactions | Automatic — one tx for all root mutations | Manual `$transaction` wiring |
| Files, webhooks, crons | Generated — storage pipeline, durable delivery, native crons | DIY |
| Programmatic tokens for your users | Built-in, immediate revocation | DIY |
| Runtime | cf-workers + Deno (Node target on the roadmap) | Node / Bun / Deno / edge interchangeably |
# codegen
gqlith vs ZenStack v3
License MIT
The closest philosophical cousin: one ZModel schema → access policies, CRUD APIs and TanStack hooks.
Why gqlithThe same schema-first, policy-first instinct — but from your live Postgres instead of a code-first ZModel, with the policies also compiled to database-enforced RLS, and GraphQL + MCP + storage surfaces ZenStack doesn’t attempt.
The full breakdown — what they do well, where we differ
ZenStack v3 (GA December 2025, MIT, now on its own Kysely-based engine) shares more philosophy with gqlith than anything else here: declare the schema and the access policies once, get the CRUD plumbing and typed frontend hooks generated. The differences are structural. ZenStack is code-first — the ZModel file is the source of truth and the database follows — where gqlith introspects the Postgres schema you already have and keeps config in SQL comments beside the data. ZenStack’s policies are enforced in the ORM/runtime layer only; they are not compiled to Postgres RLS, so a direct database connection sees everything. Its surfaces stop at REST-plus-hooks — a genuine JSON:API 1.1 handler and typed TanStack hooks, but no GraphQL, no MCP, no subscriptions, no file storage — and its typed JSON (preview) covers reads and validated writes without typed filtering or database-level enforcement. If you are starting a green-field TypeScript app and want Prisma-style modeling with policies, ZenStack is a fine choice; gqlith is built for the Postgres you already run and the surfaces an agent-era product needs.
| Capability | gqlith | ZenStack v3 |
|---|---|---|
| Source of truth | Your live Postgres schema + SQL-comment annotations | Code-first ZModel file; the database follows |
| Surfaces | GraphQL, JSON:API 1.1 REST, MCP, typed TS client SDK | JSON:API 1.1 REST + RPC handlers, TanStack Query hooks; no GraphQL, no MCP |
| Where policies are enforced | App layer and (opt-in) generated Postgres RLS — the database enforces the same model | ORM/runtime layer only; not compiled to RLS |
| Typed JSON | Typed reads, validated writes, typed filtering & ordering, CHECK / pg_jsonschema DDL, drift-check CLI | Strongly-typed JSON (preview): typed results + validated writes; no typed filtering, no DB-level enforcement |
| Realtime | SSE subscriptions, per-row authorized | None |
| Files, webhooks, crons | Generated — storage pipeline, durable delivery, native crons | None |
# caveats
Trade-offs we made on purpose
We picked surfaces over polish in a few places. None of these are deal-breakers for the audience we serve, but they should not be a surprise.
-
No Relay `nodeId` / `node(id)` yet
Cursor Connections (edges, pageInfo, totalCount) are fully supported and strict. Global Object Identification — Apollo Client cache normalization by global id, Relay `useRefetchable` — is on the near-term list.
-
No persisted operations / trusted documents
We ship depth, complexity, input-depth and body-size limits, but any syntactically valid query is accepted. A codegen-native allow-list (operations extracted at build time, manifest emitted into the server) is high on the list.
-
Generated RLS is opt-in, applied by your migrations
By default, `@gqlith.rowFilter` predicates are enforced in the generated code — portable, but a direct database connection bypasses them. Turn on `jwt.dbClaims` + `rls.generate` and gqlith emits the matching Postgres RLS policies as SQL you review and apply with your own migration tool. We generate the policies; we don't touch your database for you.
-
MCP agents write GraphQL — there are no per-entity typed tools
We replaced rigid per-table tools with four governed ones: the agent discovers the schema through `describe_schema` (permission-projected, with runnable examples) and executes GraphQL documents. Subscriptions over MCP and filter autocomplete are not shipped yet.
-
cf-workers + Deno only
Edge-first is a deliberate strength. Teams standardized on Node, Lambda or Fargate can run a Cloudflare Worker today; a native Node target widens the addressable surface and is planned.
-
Single Postgres database
No cross-database federation, no read-replica routing. Most teams we serve are on a single Postgres — we keep depth there instead of breadth across sources.
Be among the first to generate your backend.
gqlith is in private early access. Leave your email and we’ll bring you in as we open it up — a small group at a time.