This walkthrough builds a small posts app from scratch: schema in SQL, migrations drafted automatically, typed TypeScript wrappers generated from your query files. By the end you will have a workingDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/iterate/sqlfu/llms.txt
Use this file to discover all available pages before exploring further.
getPosts(client, {limit: 10}) call with full IDE types.
Install
Add sqlfu to your project:Optionally install the CLI globally so When you run
sqlfu is on your PATH anywhere:sqlfu inside a project that already has it as a dependency, the global binary delegates to the project-local copy. Each project runs the version it pinned.Initialize the project
sqlfu.config.ts, definitions.sql, migrations/, sql/, and db/. The config already points at sensible defaults:sqlfu.config.ts
If you run
npx sqlfu with no config, sqlfu will prompt you to init first. sqlfu init makes it explicit.Define your schema
Open
definitions.sql and describe the schema you want right now:definitions.sql
definitions.sql is the single source of truth for your desired schema. When you change it, sqlfu computes the diff and writes the next migration for you.Draft a migration
definitions.sql, and writes a new migration file under migrations/. Open the file and review it — the diff engine is not psychic, so check for renames and destructive changes.The generated file will look something like:migrations/20260101000000_add_posts_table.sql
Apply migrations
db/app.sqlite) and records each one in sqlfu_migrations. Run this every time you pull new migrations from the repo.Add a query
Create Query files live next to the code that calls them. The filename is the query’s identity — it shows up in generated types, observability spans, and error messages.
sql/get-posts.sql:sql/get-posts.sql
Generate types
.sql files against definitions.sql and emits typed wrappers into sql/.generated/. For get-posts.sql you get a getPosts function with:- typed params (
getPosts.Params) - typed result rows (
getPosts.Result) - static
.sqland.queryproperties (includingname: "getPosts") used by observability hooks
generate.authority is desired_schema, generation does not need a live database. The migrate step earlier is only needed so the next step can call the wrapper against db/app.sqlite.Call the wrapper
getPosts.query.name field ("getPosts") travels with every call to OpenTelemetry spans, Sentry errors, and Datadog metrics.node:sqlite is built into Node 22+. Using a different runtime or driver? See Adapters for Bun, Turso, D1, Expo, and others — the same generated wrappers work unchanged across all of them.Changing the schema
Editdefinitions.sql and add a column:
definitions.sql
definitions.sql, and emits only the statements needed to close the gap. Review the file, commit it, then run npx sqlfu migrate and npx sqlfu generate to update the live schema and regenerate the typed wrapper.
Where to go next
Runtime client
The shared
Client interface, sync vs async, prepared statements, and client.all() / client.run().Adapters
Copy-paste snippets for Node, Bun, Turso, Cloudflare D1, Durable Objects, Expo, and WASM.
SQL migrations
The replay-based migration model, what
sqlfu check verifies, and what to do when a migration fails.Type generation
@name comments, inferred IN (:ids) lists, object dot paths, and bulk insert shapes.Observability
Query names in OpenTelemetry spans, Sentry errors, PostHog events, and Datadog metrics.
Runtime validation
Opt into zod, valibot, arktype, or zod-mini validation baked into generated wrappers.