Overview
sqlb is a schema-first data layer for Go and Postgres. You declare your tables once, as ordinary Go values, and that one declaration becomes typed composable queries, a validated REST filter grammar, generated clients, and the place your domain logic hangs off.
It exists because the layer between an HTTP handler and the database is high-volume, low-novelty code that gets rewritten per resource — parse the query string, decide what a client may filter on, assemble SQL, paginate, count, serialise, thread authorisation through all of it — and it is a good place for security bugs to hide.
Five surfaces, one declaration
Section titled “Five surfaces, one declaration”Each of these is a section of this documentation, and each is independently optional. A project can take the query builder and nothing else.
| Surface | What it gives you | |
|---|---|---|
| Schema | Tables as Go values: columns, references, indexes, constraints, and the capabilities that decide what the outside world can reach | Declaring tables |
| Queries & domain logic | A query is a value, so predicates compose on a branch. Hooks are where the rules live | Queries |
| REST API | List, read, create, patch and delete per exposed table, with filtering, sorting, search, pagination and an OpenAPI document | Mounting resources |
| TypeScript SDK | A generated client where where admits only filterable columns, select narrows the response type, and a hidden column has no spelling |
TypeScript SDK |
| Dart SDK | The same vocabulary for a Flutter app, plus the cursor pager an infinite-scrolling list needs | Dart SDK |
| Go CLI | A cobra command tree over the same vocabulary, so --help states what a resource accepts without sending a request |
Go CLI |
Alongside them, Migrations turns a schema edit into files your runner applies — and turns an existing database into a schema file, which is the same machinery pointed the other way.
The one idea
Section titled “The one idea”A query is a value, not a statement that runs when you build it:
q := sqlb.Query[Post]().Where(sqlb.F("status").Eq("published"))if search != "" { q = q.Where(sqlb.F("title").Contains(search))}posts, err := q.OrderBy(sqlb.F("created_at").Desc()).Limit(50).All(ctx, db)Static query generators cannot express “this WHERE clause exists only when the user typed something in the search box”, which is why the dynamic parts end up being built by string concatenation. PostgREST solves that by making the database the API, but then there is nowhere to put Go domain logic and the whole schema sits one policy mistake away from being public.
sqlb takes the middle path: the REST filter grammar compiles into the same predicate AST your Go code produces. One compiler, one bind-parameter discipline, one set of hooks — two producers.
What to read
Section titled “What to read”New here. Quickstart goes from go get to a running
server in five steps. Your first app then walks a complete
worked one.
Deciding whether to adopt it. Concepts is the short version of the reasoning — five pages, one idea each. How sqlb compares is honest about sqlc, ent, PostgREST, Atlas and Bun, including when not to use this. Compatibility says what is frozen and what is expected to move.
Already have model structs. The schema DSL and code generation are both optional. Using your own structs layers the same capabilities over structs you already have, including stock sqlc output, without editing them. Refactoring a sqlc endpoint walks one all the way from static SQL to a generated resource, in four stages you can stop after.
Already have a database. Adopting a database reads it back into a schema file, and Refactoring a database is what happens to it afterwards — adding, renaming and dropping, and which of those break a client rather than a table.
Requirements
Section titled “Requirements”Go 1.25 or newer, and Postgres. Nothing else: the engine depends on the standard
library alone, and a check in CI enforces it. Only the rest package pulls in
Huma, and only if you use it; the generated TypeScript
client and the generated CLI are separate toolchains and separate opt-ins.
Postgres only, deliberately. LISTEN/NOTIFY, jsonb aggregation and RETURNING
are all load-bearing, and multi-dialect support would cost the best features
(ADR-0001).
Status
Section titled “Status”sqlb is pre-1.0, has one author and no observed consumers. That is the honest starting position, and no amount of feature work substitutes for elapsed time under real traffic. The adoption review is an outside read on what that means in practice.