Databases
Pick a database with --db. Your functions don't change.
--db | Instances | Notes | |
|---|---|---|---|
| SQLite | a path (default <data>/db.sqlite) | one | Fastest: reads take microseconds. The default. |
| Postgres | postgres://user:pass@host:5432/db | many | Live queries are shared through LISTEN/NOTIFY. Works with Neon, Supabase, RDS, … |
| MySQL | mysql://user:pass@host:3306/db | many | MySQL 8.0.21 or later. Schema indexes aren't used (see below). Not MariaDB. |
| Turso / libSQL | libsql://my-db.turso.io | many | Token from $TURSO_AUTH_TOKEN or ?authToken=. Also works with self-hosted sqld. |
Postgres and MySQL support are built into Bun, and the libSQL client is pure JavaScript, so all four work from the same single binary.
#SQLite
The default needs no setup. onebase uses WAL mode with a separate reader connection, so queries never wait for writes. Back it up to S3 with --backup (see Backups).
The server gives space left by deleted documents back to the filesystem by itself; a database made by an older version needs one onebase vacuum first.
SQLite can only be used by one server process. When you need more than one, switch to Postgres, MySQL or Turso.
#Postgres
onebase serve --db postgres://onebase:secret@db.internal:5432/app- Tables: onebase creates its tables on first start:
documents(withjsonb) plus a few system tables. - Indexes: each schema index becomes a
jsonbexpression index, and Postgres's planner uses it for bothwithIndexand.filter(). - Isolation: queries run in
REPEATABLE READread-only transactions, mutations inSERIALIZABLE.
#MySQL
onebase serve --db mysql://onebase:secret@db.internal:3306/app- Tables:
documents(with aJSONcolumn) plus the system tables, created on first start. - Indexes: MySQL can't index a JSON value, so schema indexes are recorded but not built.
withIndexand.filter()give the same results as on the other databases, but each query reads the table's documents and sorts them. Fine for small and medium tables; use Postgres when tables get large. - Search: each search index is a
FULLTEXTside table. MySQL skips words shorter than 3 characters and stopwords like "the". - Isolation: queries run in
REPEATABLE READread-only snapshots, mutations inSERIALIZABLE. Deadlocks are retried automatically. - Live queries across instances use a small commit log that each instance polls every 200 ms, as with Turso.
- Connecting: with MySQL 8's default
caching_sha2_passwordand no TLS, add?allowPublicKeyRetrieval=trueto the URL, or connect over TLS.
MariaDB isn't supported. Its JSON type is plain text, so it compares and sorts document fields as strings ("10" < "9"). onebase stops at startup with an explanation rather than return wrong results.
#Turso / libSQL
export TURSO_AUTH_TOKEN=...
onebase serve --db libsql://my-app-myorg.turso.ioThe schema and indexes are the same as with local SQLite. Each ctx.db call is a round trip to Turso, so put your server in the same region as the database.
#Running several instances
With Postgres, MySQL or Turso you can run as many onebase processes as you like behind a load balancer:
onebase serve --db $DATABASE_URL --files s3://bucket/files --port 8090 # machine A
onebase serve --db $DATABASE_URL --files s3://bucket/files --port 8090 # machine BWhat onebase guarantees:
- Live queries everywhere. A commit on any instance updates subscribers on all of them: through
NOTIFYon Postgres, and through a small commit log on MySQL and Turso that each instance polls every 200 ms. - No lost updates. Mutations are serializable. When two instances conflict, one of them is retried automatically. This is why side effects belong in actions, not mutations.
- Each job runs once. Each scheduled job and cron run is claimed by exactly one instance.
Use --files s3://… when you run several instances, so they all see the same files. Sticky sessions aren't required.
#How data is stored
Every document is one row: documents(id, tbl, ct, doc). The document is JSON (SQLite, MySQL) or jsonb (Postgres), ct is the creation time, and tbl is the table name. Schema indexes are partial expression indexes per table (not on MySQL). Ids carry a 4-character tag for their table. See How it works.