Skip to main content

Documentation 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.

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 working getPosts(client, {limit: 10}) call with full IDE types.
1

Install

Add sqlfu to your project:
npm install sqlfu
Optionally install the CLI globally so sqlfu is on your PATH anywhere:
npm install -g sqlfu
When you run 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.
2

Initialize the project

npx sqlfu init
This creates sqlfu.config.ts, definitions.sql, migrations/, sql/, and db/. The config already points at sensible defaults:
sqlfu.config.ts
export default {
  db: './db/app.sqlite',
  migrations: './migrations',
  definitions: './definitions.sql',
  queries: './sql',
};
If you run npx sqlfu with no config, sqlfu will prompt you to init first. sqlfu init makes it explicit.
3

Define your schema

Open definitions.sql and describe the schema you want right now:
definitions.sql
create table posts (
  id integer primary key autoincrement,
  slug text not null unique,
  title text not null,
  body text not null,
  published integer not null default 0
);
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.
4

Draft a migration

npx sqlfu draft
sqlfu replays your migration history into a scratch database, diffs it against 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
create table posts (
  id integer primary key autoincrement,
  slug text not null unique,
  title text not null,
  body text not null,
  published integer not null default 0
);
Looks right. Commit it as-is.
5

Apply migrations

npx sqlfu migrate
This applies any pending migrations to your dev database (db/app.sqlite) and records each one in sqlfu_migrations. Run this every time you pull new migrations from the repo.
6

Add a query

Create sql/get-posts.sql:
sql/get-posts.sql
select id, slug, title, body, published
from posts
where published = 1
order by id desc
limit :limit
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.
One .sql file can hold multiple queries. Use /** @name queryName */ comments to name them individually. sqlfu uses the @name as the generated function name.
7

Generate types

npx sqlfu generate
sqlfu reads your .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 .sql and .query properties (including name: "getPosts") used by observability hooks
Because the default 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.
8

Call the wrapper

import {DatabaseSync} from 'node:sqlite';
import {createNodeSqliteClient} from 'sqlfu';
import {getPosts} from './sql/.generated/get-posts.sql.ts';

const db = new DatabaseSync('./db/app.sqlite');
const client = createNodeSqliteClient(db);

const posts = getPosts(client, {limit: 10});
//    ^? Array<{id: number, slug: string, title: string, body: string, published: number}>
Params and result rows are fully typed. Your IDE hover shows the inferred row type directly. The 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

Edit definitions.sql and add a column:
definitions.sql
create table posts (
  id integer primary key autoincrement,
  slug text not null unique,
  title text not null,
  body text not null,
  excerpt text,
  published integer not null default 0
);
Then draft the migration:
npx sqlfu draft
The generated file will contain:
alter table posts add column excerpt text;
That is one line, not a full table rebuild. The diff engine compares the schema you have now (replayed from migration history) against the schema you declared in 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.

Build docs developers (and LLMs) love