Prerequisites

  • MUST Bun ≥ 1.1 — the embedded client runs on bun:ffi. Install from bun.sh.
  • MAY A Rust toolchain (1.80+) only if you build from source or are on a platform without a prebuilt binary — see Build from source. The npm path needs nothing.
  • MAY An S3-compatible bucket (AWS S3 / Cloudflare R2 / MinIO) only for the disaggregated (s3://) backend; the file:// path needs nothing.

1 · Install

Add the wrapper to your project. The native engine ships as a set of per-platform binary packages (@twilldb/engine-darwin-arm64, @twilldb/engine-linux-x64, …) declared as optional dependencies; your package manager downloads only the one matching your OS and CPU. There is no build step, no postinstall, and no Rust toolchain.

bun add @twilldb/bun

Adding to a real app?

This page is the fast path to a running query. To wire Twill DB into a new or existing project — a reusable db module, env-driven connection string, .gitignore — or to hand the setup to an AI coding agent, see Add to a project.

2 · Your first embedded database

Open a file:// database and run SQL at function-call latency. open(url) returns a handle; the using declaration disposes it at scope exit. Values come back as strings (a SQL NULL as null) — cast on the application side if you need numbers.

// notes.ts
import { open } from "@twilldb/bun";

using db = open("file://./local.db");          // pure-embedded, zero network

db.exec(`CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)`);
db.query("INSERT INTO notes VALUES (?, ?)", [1, "hello"]);  // parameterized writes go through query()

const rows = db.query("SELECT id, body FROM notes");
console.log(rows);                              // [{ id: "1", body: "hello" }]
bun run notes.ts

The database persists to ./local.db and is rebuilt by replaying the durable WAL on reopen, so your data survives a process restart. Use ? placeholders with a value array for any user input — values never touch the SQL text. See Embedded (bun:ffi) for the full API: prepared statements, transactions, and resource management.

3 · Go disaggregated (optional)

To make the same code path disaggregated and scale-to-zero, change only the connection string — no recompile, no code change.

using db = open("s3://my-bucket/notes");        // disaggregated; durable on object storage

Provide bucket credentials through the environment (never in code). See Connect to your database for the supported schemes and Scale-to-zero for the lifecycle.

4 · Start a server (optional)

Prefer a wire protocol over the in-process library? Run the same engine behind a Postgres-wire listener and connect with any Postgres client (cleartext / sslmode=disable). This path is built from the repo with Cargo.

cargo run -p twill-server -- --listen 127.0.0.1:5433 --db file://./srv.db   # or s3://bucket/db

psql "host=127.0.0.1 port=5433 user=postgres sslmode=disable"
// Or from Bun's built-in client:
import { SQL } from "bun";
const sql = new SQL("postgres://[email protected]:5433/srv?sslmode=disable");
const rows = await sql`select 1 as n`;

See Postgres client and the per-language guides under Connect as server for Node, Python, Go, Drizzle, Prisma, and PostgREST.

5 · Branch your data

A branch is an instant copy-on-write fork: it sees the base's data and writes in isolation. Creating one copies no pages.

using preview = db.branch("preview");
preview.exec("INSERT INTO notes VALUES (2, 'branch-only')");
// the base never sees the branch's write, and vice versa

Build from source

You only need this if you are contributing to the engine, running on a platform without a prebuilt binary, or testing an unreleased change. Building produces the static/dynamic libraries plus the frozen C ABI header; the wrapper's loader then picks the library up from the cargo target dir automatically (or set TWILLDB_ENGINE_PATH to an explicit path).

git clone https://github.com/bihaviour/twill-db
cd twill-db

cargo build -p twill-engine --release
# → target/release/libengine.{a,so,dylib}  +  crates/engine/include/engine.h

cd clients/bun && bun install && bun test   # end-to-end embedded test suite

Rebuild after engine or ABI changes

The wrapper loads the native library through bun:ffi and pins an expected ABI version. After any change that touches the C ABI or engine behaviour, rebuild the release libengine before running the client — otherwise it runs against a stale binary.

Next steps

Twill DB documentation · Licensed under BUSL-1.1. · Author