AstroBaaS

Run it

Storage drivers

Generated from STORAGE.md in the AstroBaaS repository. The repository is the source of truth; this page is a copy of it.

AstroBaaS persists through a pluggable adapter chosen at boot from the environment. All application code depends on the Storage contract (src/core/storage.ts), so the engine is swappable without touching routes, plugins, or the admin.

Drivers

DATABASE_URLDATABASE_DRIVERDriverUse for
(unset)lowdb (JSON file at DB_PATH)Zero-config local dev.
file:./data/astrobaas.db(unset)libSQL doc-blob (local SQLite)Durable single-host / VPS.
libsql://<db>.turso.io (+ DATABASE_AUTH_TOKEN)(unset)libSQL doc-blob (remote / Turso)Durable, deploy-portable.
file: or libsql://…relationalrelational libSQL (per-entity rows)Production multi-writer / multi-host.

All three are exercised by the smoke suite: npm run smoke (lowdb), npm run smoke:libsql (doc-blob), npm run smoke:relational (relational) — and all three run in CI.

Set DATABASE_DRIVER=relational (with a file: or libsql:// DATABASE_URL) to use the per-entity engine (src/lib/storage/sql-storage.ts): each collection is its own table of (id, data) rows. Versus the doc-blob adapter this gives:

  • Row-level concurrency — two writers touching different rows never clobber each other (the doc-blob model is whole-document last-write-wins).
  • Partial updates — a write touches one row, not the entire dataset.
  • Queryability — indexed columns / json_extract filters instead of loading everything into memory.

It implements the same Storage surface, so LocalDB delegates to it with no route/plugin/admin changes. Entities are stored as JSON in a data column — real per-row storage without hand-maintaining a column per field while the alpha schema still moves; hot lookups (by email, key hash, slug, setting key) use json_extract.

# Production example (Turso, relational)
DATABASE_URL=libsql://my-app.turso.io
DATABASE_AUTH_TOKEN=...           # from `turso db tokens create`
DATABASE_DRIVER=relational
AUTH_SECRET=$(openssl rand -hex 32)
NODE_ENV=production

Why this matters for deployment

The lowdb JSON file lives on local disk, so it can’t be shared across instances and is wiped on ephemeral/serverless filesystems. Pointing DATABASE_URL at a remote libSQL/Turso database gives durable, network-attached storage that survives redeploys and works when more than one instance is running — which is what a hosted/serverless deployment of a vibe-coded frontend needs.

# Production example (Turso)
DATABASE_URL=libsql://my-app.turso.io
DATABASE_AUTH_TOKEN=...           # from `turso db tokens create`
AUTH_SECRET=$(openssl rand -hex 32)
NODE_ENV=production

On first boot against an empty database, AstroBaaS seeds the schema defaults and the admin@local account automatically (same as the JSON path) — no migration step to run.

Choosing between the libSQL drivers

  • doc-blob (DATABASE_URL only): the whole dataset is one JSON row. Durable and deploy-portable, but writes are last-write-wins across instances (fine for a single long-lived process; risky for multi-instance write traffic).
  • relational (+ DATABASE_DRIVER=relational): per-entity rows with row-level writes — the right choice for multi-writer / multi-host. Recommended for production.

The relational engine stores each entity as a JSON data column rather than a fully-normalized column-per-field schema. That’s a deliberate alpha trade-off (real per-row concurrency now, without freezing the still-evolving field set); a fully-normalized schema with typed columns + foreign keys is a future refinement and, because everything goes through the Storage interface, drops in without touching application code.

Migrating data between drivers

Use the built-in backup tools (Admin → Tools, or /api/backup/export/api/backup/import): export from one driver, point DATABASE_URL at the new one, import. The backup format is engine-independent JSON.