Skip to content

How sqlb compares

Every evaluator builds this table silently. Better to build it here, including the places where sqlb loses.

Read the status first, because it outranks everything below: sqlb is pre-1.0, has one author and no observed consumers. Every tool on this page is older, more used, and more likely to still exist in three years. Nothing here argues otherwise.

Most of this page is not either/or. Three of the five tools below sit happily beside sqlb in one codebase, and one of those pairings is tested here rather than asserted.

Tool Strongest at How sqlb relates
sqlb Filter/sort/search endpoints whose predicates vary per request, and one constraint applied to every read of a model
sqlc Static queries typed against the real schema, and anything expressible in SQL Complementary, and tested that way: sqlc owns the static queries, sqlb the dynamic list endpoint, both on one transaction
Atlas Migrations as a product — multi-engine, declarative and versioned, linted in CI Complementary: sqlb writes migration files, Atlas is a better tool for applying and linting them
Bun A mature multi-dialect query builder and light ORM Overlapping at the builder. sqlb adds the URL grammar and the capability model above it
ent A mature graph model — traversal, M2M, nested eager loading — with an extension ecosystem Overlapping, and the more complete answer today. sqlb layers over structs ent cannot touch
Drizzle Schema-as-TypeScript with compile-time inference, for a TS backend The same philosophy in another language. Not a rival for a Go backend — the question it decides is whether the backend should be Go at all
PostgREST An API with no Go code at all An alternative: the same job with the rules in RLS rather than in Go

The sections below take each in turn, leading with what it does better.

These are not compared at length below, because sqlb’s relationship to each is one line. They are here because leaving them out does not stop anyone reaching for them — it only stops this page being useful when they do. Star counts and importer counts checked August 2026.

Tool What it is How sqlb relates
GORM The default Go ORM. ~40k stars, ~87,000 importers — the most-used database library in Go by a wide margin An alternative, and the first comparison most evaluators make. GORM is a full ORM with associations, callbacks and auto-migration; sqlb is a builder plus an HTTP surface and declines to be an ORM. If your team wants an ORM, this is the one, and the when not to use sqlb list already says so
go-jet/jet Type-safe SQL builder generated from the live database. ~3.8k stars Overlapping at the builder, and stronger there: jet’s column references are generated types, so a misspelling is a compile error rather than sqlb’s runtime one. No HTTP layer, no capability model
stephenafamo/bob Query builder and ORM generated from the schema, the modern successor to SQLBoiler. ~1.8k stars, ~69 importers Overlapping at the builder. Its importer count is in sqlb’s range rather than ent’s, which is worth saying plainly after this page’s opener claims everything here is more used
squirrel The classic fluent builder, usually paired with sqlc for the dynamic half. ~8k stars, no commit since April 2024 Overlapping, and the pairing sqlb is proposing to replace: squirrel gives conditional predicates and nothing above them, and its maintenance has stopped
prest “PostgREST in Go” — a standalone binary exposing tables over HTTP. ~4.6k stars An alternative to rest, and an honest contrast: GO-2025-3941 is a systemic SQL-injection advisory against it, found by an independent review. sqlb’s answer to that class is structural — every value is a bind parameter and every identifier is validated against the model — which is a claim worth checking rather than believing
The incumbent Huma or oapi-codegen for the handlers, hand-rolled query-parameter parsing for the filters, openapi-typescript or hey-api for the client This is the workflow sqlb replaces, and the one most projects are actually on. It works. What it costs is that the allow-list, the rejection messages and the client are three hand-maintained things that drift from the schema independently — which is the whole of sqlb’s argument, and why rest is built on Huma rather than against it

What it does better. sqlc reads your actual SQL and generates types from it, checked against the real schema at build time. That is a stronger guarantee than sqlb offers and it is not close: sqlb.F("titel") is a runtime error, and the typed column facade narrows that without closing it. Anything expressible in SQL — CTEs, window functions, DISTINCT ON — sqlc handles by definition, where sqlb hands you Raw.

Where it structurally cannot help. sqlc requires complete, static SQL. A WHERE clause that exists only when a search box is filled in is not a query it can generate, and the documented workarounds are sqlc.narg with coalesce, chains of (@x::text IS NULL OR col = @x), or “use a query builder”. The last one is the honest answer, and it is where sqlb starts.

These are not competitors. docs/with-sqlc.md is the coexistence story, and example/withsqlc tests it against real sqlc output: sqlc owns the static queries, sqlb owns the dynamic list endpoint, and DB.Tx() lets both share one transaction. If you already use sqlc, that is the cheapest way to try this, and Refactoring a sqlc endpoint is the step-by-step version of moving the first one — including what it costs, which at the first step is more code rather than less.

The comparison that matters most, because ent overlaps the largest part of the pitch and does it with Meta’s name, years of production use and an extension ecosystem.

What ent does better.

  • Relations are a graph, not a column. edge.To/edge.From with Ref and Unique express O2O, O2M, M2O and M2M, and generate traversal predicates (HasPetsWith(...)) and chained traversal. sqlb has one-directional references and one level of forward ?expand; there is no reverse expansion, no M2M vocabulary, and no relation-spanning predicate — see ADR-0022 for what is missing and why.

  • Maturity. ~17.2k stars and ~4,000 importers on pkg.go.dev, production use at scale, and an extension for this exact job: entrest generates an OpenAPI spec and an HTTP handler implementation with filtering, pagination and eager-loaded edges, which covers a large fraction of what rest does here.

    Two corrections to what this page used to say, both of which cut against sqlb’s case in one direction and for it in the other. entoas is spec-only — “Generate a fully compliant, extendable OpenAPI Specification document”, with the README pointing elsewhere for a server — so only entrest generates handlers. And entrest is one maintainer, 41 stars and created in June 2024, which is a thinner “ecosystem” than the word implies, and thinner than ent itself by a wide margin.

  • The privacy layer is the same idea as BeforeQuery, arrived at first. Opt in per schema, and it then applies to every query and mutation of that type regardless of call site.

Where sqlb differs, and one place it is genuinely better.

Describe[T] has no ent equivalent. ent cannot be layered over structs it did not generate, which makes it all-or-nothing per project. sqlb can be pointed at structs another tool produced — including stock sqlc output — and adds capabilities without editing them.

Capability opt-in also lives in the core schema here rather than in an extension’s annotations, so one vocabulary drives both the SQL layer and the HTTP surface. In ent, field.Sensitive() is roughly Hidden, but the filterable/sortable exposure that shapes a REST endpoint is entrest’s annotations — a separate extension with its own release cadence.

One concrete difference worth knowing, because it cuts both ways. ent’s eager loading issues additional queries: “it is not possible to load all associations in a single JOIN operation. Therefore, Ent executes additional query to load each association.” sqlb expands in one statement, a LEFT JOIN and a json_build_object per relation (ADR-0025).

sqlb’s version is consistent by construction — one snapshot, so a foreign key and its expansion cannot contradict each other. ent’s version inherits its privacy rules on the target for free, because the second query is an ordinary read of that type. sqlb’s does not: a BeforeQuery hook on the target does not apply to an expansion of it, which ADR-0025 concedes as the cost of the design. If your boundary is enforced by a hook rather than by the schema, that is a real difference and it is not in sqlb’s favour.

What it does better. No Go code, no application layer to write, and years of production use. If the API you want is a faithful projection of your tables and the rules are expressible as row-level security, PostgREST is less work than anything on this page.

(This page used to say “no deployment beyond the database”, which is not true: PostgREST is its own Haskell server process to deploy, configure and operate. The argument does not need the overstatement — what it saves you is the application, not the process.)

The trade. Authorization is Postgres roles and RLS; there is no application layer, so business rules become RLS policies, SECURITY DEFINER functions or views. That is a legitimate architecture and it is the one sqlb declines: the schema sits one policy mistake away from being public, and there is nowhere to put Go domain logic.

sqlb’s answer is the inverse. A column is unreachable unless it declares a capability, and the failure is a 400 naming what would have been accepted rather than a leak — and BeforeQuery is a place for the rules that RLS would otherwise have to carry.

What it does better. Nearly everything about migrations. Atlas is a language-independent schema tool with declarative and versioned workflows, diffing, destructive-change detection, lock-aware linting, ORM integration and CI/CD across engines. It is a company’s entire product.

What sqlb’s migrate actually is. A diff between two registries, rendered as Postgres DDL, written as files for a runner you already have — with lock-aware sequencing for the changes whose remedy is mechanical. It does not apply migrations and does not track which have run.

If migrations are the problem you are solving, use Atlas. sqlb’s migration layer exists so that a schema declared in Go can reach the database at all, not to compete with a dedicated tool. The two are compatible: Atlas can consume the SQL files sqlb writes.

One thing that changed, and that this recommendation should carry. Since v0.38 (October 2025), atlas migrate lint is Atlas Pro only — $9 per developer per month plus $59 per CI project per month. The linting is the part of Atlas this page was pointing at hardest, so “use Atlas” now has a price tag on it. Everything else above still holds, and sqlb’s own lock-aware sequencing covers a much narrower set of changes than a linter does.

What it does better. A mature, widely used query builder and light ORM, across Postgres, MySQL, MSSQL and SQLite. It solves the conditional-predicate problem the same way sqlb does — that is what a builder is for — and it has years of use behind it.

What it does not have. No HTTP layer, no OpenAPI generation, and no per-column capability model. Building a filter endpoint on Bun means writing the parameter parsing, the allow-lists and the rejection messages yourself. That work is the thing sqlb is trying to delete, and it is also the work where a missing allow-list becomes a leak.

The sharper difference, for a Postgres project. Bun is built on database/sql — you hand bun.NewDB a *sql.DB and a dialect. sqlb is pgx-native since ADR-0040. So Bun cannot join a pgx.Tx: an application already holding one for its pgx or sqlc code cannot put a Bun query inside it without going through database/sql for everything. That is the whole of the sqlc coexistence story above, and it is not available on the other side.

The one entry on this page that is not a Go library, and it earns its section by being the closest philosophical relative here — closer than anything in Go. Drizzle (~35k stars, checked August 2026) is “headless ORM for NodeJS, TypeScript and JavaScript”: the schema is ordinary TypeScript in your codebase, and drizzle-kit diffs migrations from it (generate), applies them (migrate/push) and introspects an existing database back into a declaration (pull). Schema as code in the application’s own language, migrations derived rather than written, adoption by introspection — an evaluator who likes sqlb’s shape will recognise all of it, which is why the comparison belongs here even though nobody chooses between them for one backend. What this section decides is a prior question: if your whole stack is TypeScript, should the backend be Go at all?

What Drizzle does better, and Go cannot match. Type inference. A Drizzle schema types every column, every query result and every insert at compile time, for free — no codegen step, no generated files to gate. db.select() against a misspelled column is a squiggle in the editor. sqlb’s equivalent is a runtime error narrowed by the typed column facade and caught by Explain-as-a-gate in CI, and the jet/bob answer is codegen. None of the three is as good as what TypeScript’s type system hands Drizzle for nothing. It is also multi-dialect — Postgres, MySQL, SQLite and a long list of serverless drivers — where sqlb is Postgres only, and it has the ecosystem this page keeps conceding: Drizzle Studio, first-party integrations, and a community sqlb does not have.

Where Drizzle stops, and sqlb’s argument starts. Drizzle derives types and migrations from the schema and nothing else. The HTTP surface is explicitly your problem — the filter parsing, the allow-lists, the OpenAPI document, and the API-boundary validation are assembled per project from pieces (drizzle-zod for schemas at the boundary, tRPC or a framework for the transport). There is no per-column capability model, so “which columns may a request filter by” is a hand-maintained list with all the drift that implies, and there is no counterpart to the Scoped obligation — a forgotten tenant predicate is a leak, not a boot failure.

The sharper difference is who the derivation protects. Drizzle’s inference protects one language, end to end, superbly: a TS schema change breaks the TS code that contradicts it, including the frontend if types are shared. It protects nobody else — a Dart app, a Go worker, an agent reading the API are all outside the inference boundary and drift silently. sqlb inverts the trade: weaker guarantees inside the backend language, and generated, drift-gated clients for four languages outside it. Which trade is right is a fact about your consumers, not about the tools.

Stripped down, one thing on this page is not done elsewhere:

A single predicate AST that a Go query builder and an HTTP query grammar both compile into, with per-column capability opt-in, so a BeforeQuery hook constrains reads from both — and an unlisted column is a 400, not a leak.

  • PostgREST gives the grammar and no place for Go domain logic.
  • sqlc cannot express a conditional predicate at all.
  • Bun and the typed builders give the builder and nothing above it.
  • Drizzle gives the schema-as-code and the derived migrations, in TypeScript, and stops before the HTTP surface — the allow-lists, the boundary validation and the transport are assembled from pieces per project.
  • ent plus entrest is the one real counter-example, and the honest version of this claim has to name it: entrest compiles URL filters into ent predicates that pass through ent’s privacy layer, which is the same shape. What is left is narrower — not done elsewhere in one tool, over structs you did not generate, with the capability vocabulary in the core schema rather than in an extension’s annotations.

Second, smaller: Explain-as-a-gate. It validates a query against the live schema without running it, so it fails on a migration that was written and never applied — which a compile-time column check cannot see. It costs a database in CI, which sqlc’s approach does not.

Stated plainly, because a comparison page that cannot answer this is marketing:

  • You need it to still exist in three years. One author, no consumers, no production history. Use ent.
  • Your queries are mostly static. The thing sqlb is for does not arise, and sqlc gives a stronger guarantee for that work.
  • You need a graph. Many-to-many, reverse expansion, traversal predicates — ent has them and sqlb does not.
  • Migrations are the problem. Atlas.
  • You are not on Postgres. sqlb is Postgres-only and will stay that way.
  • Your team wants an ORM. This is not one, and does not intend to be.
  • Your whole stack is TypeScript and so are all your consumers. Drizzle gives the same schema-as-code philosophy with the compile-time inference Go cannot have, and sqlb’s remaining edge — generated clients for the languages inference does not reach — is an edge you would not be using.

Claims about other projects were checked against their documentation, their repositories and pkg.go.dev in August 2026, and carry links. They will go stale; if you find one that has, please open an issue — an out-of-date comparison is worse than none. Issue #79 is what that looks like working, and four of the six things it corrected made sqlb’s case weaker rather than stronger.