Connect to your database
There is one engine and two ways to reach it — embedded (in-process) or server (over the Postgres wire protocol) — and one connection string decides where it durably writes. This page explains the connection model so the rest of the Connect section is just detail.
Two front doors, one engine
Pick how your application talks to the engine. The choice is about access shape, not about a different database:
| Mode | How you connect | Best for | Latency |
|---|---|---|---|
| Embedded | Link the native library; call it over the C ABI (bun:ffi / @twilldb/bun) | A single app that owns the database; edge / offline; lowest latency | Function call (no socket) |
| Server | Connect a Postgres client to engine-server over the wire protocol | Multiple clients; existing Postgres tooling; REST via PostgREST | Network round-trip |
Server mode is the same library, wrapped
The server is the embedded engine behind a network listener — there is no second engine. You can develop embedded and deploy as a server (or vice versa) without changing your SQL or your data.
The connection string selects the backend
Both modes open the database with a URL whose scheme selects the durable storage backend. Unknown schemes are rejected — never silently defaulted.
| Scheme | Backend | What it gives you |
|---|---|---|
file://path/to.db | LocalFileStorage | Pure-embedded, zero network. Dev, offline, edge, single-node. |
s3://bucket/db | ObjectStorage(LSM + S3-CAS log) | Storage-disaggregated and scale-to-zero. Durability bottoms out on object storage; the local cache keeps reads off the network hot path. |
r2://bucket/db | ||
gs://bucket/db |
Same binary, no rebuild
Switching from file:// to s3:// is a connection-string change, not a recompile. The engine and the C ABI are unchanged; only the backend behind the storage seam differs.
Connection-string anatomy
Embedded callers pass the URL to open(). Server callers use a standard Postgres URI pointing at the listener; the server itself was started with the storage URL.
// Embedded — the storage URL is the connection string
open("file://./local.db");
open("s3://my-bucket/app"); // bucket credentials come from the environment
# Server — start it with the storage URL, connect with a Postgres URI
cargo run -p twill-server -- --listen 127.0.0.1:5433 --db s3://my-bucket/app
psql "host=127.0.0.1 port=5433 user=postgres sslmode=disable"
Choosing a backend
- SHOULD use
file://for development, tests, examples, embedded single-node apps, and edge/offline use — it needs no infrastructure and keeps the hot path entirely in-process. - SHOULD use
s3:///r2:///gs://when you want disaggregated durability and scale-to-zero: stateless compute that idles to nothing while only object-storage bytes bill at rest. - MAY prefer Cloudflare R2 (
r2://) where egress cost matters — a cold cache reads from object storage, and R2's zero egress makes those misses structurally cheaper.
Security
- MUST keep real bucket credentials in the environment, never in code, tests, or examples — those use
file://only. - MUST treat the connection string as configuration: it determines durability and single-writer fencing, both of which are security properties of the engine.