Rado GitHub

Migrations

Rado takes a direct approach to schema management: describe your schema in code, then ask the database to match it.

Creating from scratch

db.create emits create table statements (plus indexes, enums, views, schemas) for everything you pass it:

await db.create(User, Post)

Dropping is the mirror image:

await db.drop(Post, User)

Auto-migration with db.migrate

For an existing database, db.migrate diffs each table you pass against what's actually in the database and applies the difference:

await db.migrate(User, Post)

What it handles:

  • creating missing tables
  • adding, removing and altering columns
  • adding, removing and updating indexes and constraints
  • on SQLite, recreating the table when an incompatible change requires it (data is carried over)

Everything runs inside a transaction: either the schema ends up matching, or nothing changed.

Because it's plain JavaScript talking to your driver, it works everywhere rado works: Node.js, Bun, Deno, Cloudflare Workers and the browser (PGlite/sql.js). No CLI, no .sql files on disk, no filesystem at all.

A word of warning

db.migrate is declarative and destructive. If your code no longer mentions a column, the migration drops it along with its data. That's the contract: the database ends up matching the code.

Treat it accordingly:

  • great for development, tests, embedded databases, local-first apps and small projects
  • for production databases you care about, prefer reviewing changes: use a dedicated migration tool such as dbmate and keep rado for querying
  • back up before migrating anything irreplaceable

How renames are detected

A renamed property with an explicit column name keeps its identity:

// before
const T = table('t', {fieldA: varchar('field_a', {length: 10})})
// after: property renamed, column name kept, no data loss
const T = table('t', {fieldB: varchar('field_a', {length: 10})})

A changed column name is a drop-and-add. When in doubt, inspect what would run (see below).

Running raw migration SQL

For hand-rolled migrations the sql tag and db.run have you covered:

await db.run(sql`alter table ${User} add column "bio" text`)

Combined with db.batch and db.transaction, that's enough to build your own migration runner with versioning if you need one.