Schema and migrations

Version the schema with migrations

Create ordered SQL migration files and roll out additive changes before code depends on them.

Never create tables by hand in production. D1 migrations are ordered SQL files, committed next to the application code. Anyone can rebuild an empty database from that history alone.

Create the first one:

npx wrangler d1 migrations create notes create_notes
# ✅ Successfully created Migration '0001_create_notes.sql'

Wrangler writes a numbered file into the migrations directory. Put the SQL from the previous lesson inside it, then apply it locally:

npx wrangler d1 migrations apply notes --local
# 🌀 Executing on local database notes...
# ┌─────────────────────────┬────────┐
# │ 0001_create_notes.sql   │ ✅     │
# └─────────────────────────┴────────┘

When the code that needs the new schema is ready to deploy, apply the same files with --remote. Wrangler tracks which migrations already ran in a bookkeeping table, so each file runs once, always in order. npx wrangler d1 migrations list notes --local shows what is still pending.

Two habits keep this system honest.

Never edit a migration that has already been applied anywhere. It won’t run again, so your file and the real schema silently diverge. Write a new migration instead.

And always apply locally before remotely. Every time.

Roll out additive changes first

Deploys are not instant. For a short window the old and the new Worker version both serve traffic against the same database. Don’t assume they disappear at the same instant.

So prefer additive changes. Add a nullable column or a new table, deploy code that works with and without it, backfill if you need to, then tighten or remove the old structure in a later migration:

-- 0002_add_archived_at.sql
alter table notes add column archived_at integer;

The old Worker ignores the new column and keeps working. Compare that with renaming a column the live code still reads. That takes production down for exactly the length of your deploy window, which is the worst possible timing.

Be careful with ALTER TABLE in SQLite. It supports fewer operations than Postgres, so bigger reshapes become create a new table, copy the rows, swap. Keep each migration small and read the SQL before it touches remote.

Try this now: create the first migration for the notes schema, apply it locally, list the migration state. Then delete .wrangler/state and rebuild the database from the committed files alone. If it comes back identical, your schema lives in Git, where it belongs.

Lesson completed