Add to a project
The Quickstart gets you a running query. This page wires Twill DB into a real codebase — a fresh project from scratch, or an app you already have — with a reusable db module, an env-driven connection string, and the small habits (resource cleanup, .gitignore) that keep it tidy. The last section is a paste-able prompt that lets an AI coding agent do the wiring for you.
Pick a path
There are two ways to talk to the engine. They share the same SQL and the same connection-string model, so you can start embedded and add a server later without rewriting queries.
| Path | How it runs | Reach for it when |
|---|---|---|
Embedded (@twilldb/bun) | The engine links in-process via bun:ffi; queries are function calls, no network. | You run on Bun and want the lowest latency and zero infrastructure — a single app, a CLI, an edge/serverless function. |
| Server (Postgres wire) | The same engine behind a Postgres-wire listener; any Postgres driver/ORM connects. | You are not on Bun, want to use an existing Postgres ORM, or need many processes to share one database. |
This page covers the embedded path. For the server path, start at Postgres client and the per-language guides under Connect as server.
A · New project from scratch
Scaffold a Bun project and add the package. The prebuilt engine arrives with it — no Rust, no build step.
mkdir my-app && cd my-app
bun init -y
bun add @twilldb/bun
Create a single module that owns the database handle so the rest of your code imports one ready-to-use instance:
// src/db.ts
import { open } from "@twilldb/bun";
// One connection string drives the backend. Default to a local file;
// override with TWILLDB_URL (e.g. s3://my-bucket/app) in production.
export const db = open(process.env.TWILLDB_URL ?? "file://./data/app.db");
// Create tables once, on startup. DDL runs in autocommit only.
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY,
body TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0
)
`);
// src/index.ts
import { db } from "./db";
// Parameterized writes go through query() (exec takes no params); it returns
// the (empty) result set for an INSERT.
db.query("INSERT INTO notes (id, body) VALUES (?, ?)", [1, "first note"]);
const rows = db.query("SELECT id, body, done FROM notes WHERE done = ?", [0]);
console.log(rows); // values come back as strings: [{ id: "1", body: "first note", done: "0" }]
bun run src/index.ts
Keep the data file out of git
The file:// backend writes a database file (here ./data/app.db) plus its WAL. Add the data directory to .gitignore — e.g. echo "data/" >> .gitignore — so local data never gets committed.
B · Add to an existing app
If you already have a Bun project, you only need the package and a db module. Nothing else in your build changes.
bun add @twilldb/bun
echo "data/" >> .gitignore
Drop in the same src/db.ts module shown above and import db wherever you query. A common shape — wiring it into an HTTP handler:
import { db } from "./db";
Bun.serve({
port: 3000,
routes: {
"/notes": {
GET: () => Response.json(db.query("SELECT id, body, done FROM notes")),
POST: async (req) => {
const { id, body } = await req.json();
db.query("INSERT INTO notes (id, body) VALUES (?, ?)", [id, body]);
return new Response(null, { status: 201 });
},
},
},
});
Framework-specific wiring (Hono, Elysia, Next.js, plain Bun HTTP) is covered under Connect as embedded — see the overview.
One writer per database
The engine is single-writer per database. Within one Bun process, multiple imports of src/db.ts share the same handle — that is the intended pattern. Do not open the same file:// path from several independent OS processes at once; if you need multi-process access, run the server instead. See Connection pooling for the details.
Patterns that keep it clean
- SHOULD Own the handle in one module (the
db.tssingleton above). Importing it elsewhere reuses the same in-process database — don't callopen()per request. - SHOULD Use
?placeholders with a value array for anything user-supplied — values never touch the SQL text. Noteexec(sql)takes no parameters, so parameterized statements (including writes) go throughquery(sql, params)or aprepared statement. Every column comes back as a string (SQLNULL→null); cast to numbers/dates yourself. - SHOULD Wrap multi-statement writes in
db.transaction(fn)— it commits on return (blocking until the WAL is durable) and rolls back iffnthrows. - MAY Move the connection string to
TWILLDB_URLso the same code runs onfile://locally ands3:///r2:///gs://in production with no edit. Credentials come from the environment, never code. - MAY Use
usingfor short-lived handles (scripts, tests) so they dispose at scope exit; a long-lived server handle can live for the process lifetime.
// transaction: all-or-nothing, durable on return
db.transaction((tx) => {
tx.query("UPDATE notes SET done = 1 WHERE id = ?", [1]);
tx.query("INSERT INTO notes (id, body) VALUES (?, ?)", [2, "follow-up"]);
}); // ← COMMIT returns only after the WAL is fsync'd
With an AI coding agent
Using Claude Code, Cursor, or a similar agent? Paste one of the blocks below into your agent. They carry the few facts an agent needs to get the integration right the first time — the npm package name, the Bun requirement, the connection-string model, and the engine's actual behaviour (strings out, autocommit DDL, single writer).
New project
Set up Twill DB as the database for a new Bun project.
Requirements & facts:
- Runtime is Bun (>= 1.1). The package is `@twilldb/bun` on npm; install with
`bun add @twilldb/bun`. A prebuilt native engine ships as optional deps — no
Rust, no build step, no postinstall.
- Open a database with `open(url)` from "@twilldb/bun". Use a connection string
from `process.env.TWILLDB_URL`, defaulting to "file://./data/app.db".
- Create ONE module `src/db.ts` that calls `open(...)` once and exports the
handle; everything else imports it. Do not call open() per request.
- API: `db.exec(sql)` for statements with NO parameters (DDL, simple writes;
returns rows affected). `db.query(sql, params?)` for reads AND for any
parameterized statement — exec() does not take params, so writes with `?`
placeholders go through query() (or a prepare()d statement). `db.transaction(fn)`
wraps multi-statement writes.
- Behaviour to respect: every value comes back as a STRING (SQL NULL -> null),
so cast numbers/dates yourself. DDL (CREATE/DROP TABLE) runs in autocommit
ONLY — never inside transaction(). Always use `?` placeholders with a value
array (via query/prepare) for user input.
- Add `data/` to .gitignore (the file:// backend writes a db file + WAL there).
Deliver: package install, `src/db.ts` with schema bootstrap, and a small
`src/index.ts` that inserts and queries a row to prove it works.
Existing project
Integrate Twill DB into this existing Bun project as its database.
Requirements & facts:
- Install `@twilldb/bun` with `bun add @twilldb/bun` (prebuilt engine ships with
it — no Rust/build step). Runtime must be Bun >= 1.1.
- Add a single `db` module: call `open(process.env.TWILLDB_URL ?? "file://./data/app.db")`
once and export the handle. Reuse it everywhere; never open() per request.
- Find where the app currently reads/writes data and route it through:
`db.exec(sql)` (no-parameter statements/DDL), `db.query(sql, params?)` (reads
AND any parameterized statement — exec() takes no params), and
`db.transaction(fn)` (multi-statement writes, durable on return).
- Respect engine behaviour: values come back as STRINGS (NULL -> null); DDL is
autocommit-only (keep CREATE/DROP TABLE out of transaction()); always bind
user input with `?` placeholders + a value array.
- The engine is single-writer per database within one process — the shared
module handle is correct; do not open the same file:// path from multiple
processes. Add `data/` to .gitignore.
Deliver: the db module, the migrated data access, and a note on what changed.
Point your agent at the source of truth
For deeper tasks, also give your agent these pages: Embedded (bun:ffi) (full API), SQL reference (the supported SQL surface — it is a focused subset), and Connect to your database (backends). That keeps the agent from assuming full Postgres SQL.