Migrations

Auto vs. explicit, when to switch.

6 minmedium

Grit's default migration strategy is GORM AutoMigrate: run it on boot, GORM reconciles the schema with your struct definitions. Magical when it works. This lesson covers when to keep it and when to switch.

What AutoMigrate does

apps/api/internal/database/database.go (excerpt)
db.AutoMigrate(
&models.User{},
&models.Customer{},
&models.Invoice{},
&models.LineItem{},
)

For each struct, GORM:

  • Creates the table if it doesn't exist
  • Adds new columns when you add struct fields
  • Adds new indexes when you add new gorm:"index" tags
  • Adds new foreign keys when you add relations

What AutoMigrate doesn't do

  • Remove columns — you can delete a struct field but the column lingers. Manual ALTER TABLE DROP COLUMN.
  • Rename columns — it would see the rename as "drop A, add B" and lose data. Use raw SQL with a manual rename.
  • Change column types — only certain narrowings work; widening usually does, narrowing rarely does.
  • Data migrations — backfilling values when a new column lands. AutoMigrate handles schema; YOU handle data.
AutoMigrate is great for development and for products that don't run on multiple workers simultaneously. For production with multiple API replicas, run migrations as a separate, one-time step BEFORE boot — not during.

Running migrations explicitly

Grit ships grit migrate as a CLI command that calls AutoMigrate on the same model list. In production:

Terminal
# in your deploy script, BEFORE starting the new API binary
$grit migrate
# then start the API
$./api-binary

This way the migration only runs once per release (not per worker on boot), and a failed migration aborts the deploy before serving traffic.

When to switch to explicit migrations

  • You need to do data migrations (backfills, splits, joins)
  • You need to rename columns without dropping data
  • You ship to customers who self-host (they need versioned migrations they can roll back)
  • You need a review trail — explicit SQL files in git that's reviewable

The graduation path: add golang-migrate/migrate or pressly/goose, put SQL files in internal/database/migrations/, run them from a CLI. Grit doesn't prescribe one — pick what fits your team.

The pragmatic middle: AutoMigrate + occasional raw SQL

Most Grit projects start with AutoMigrate, then run an occasional manual ALTER TABLE for renames + drops. That's fine for teams of 1–5 with a clear deploy process.

Quick check

You renamed a field from `Total` to `TotalCents` in your Invoice struct. You ran `grit migrate`. What's in the DB?

Try it

Trigger the rename gotcha on your bench-api:

  1. In your Invoice model, rename a field — e.g., Total → TotalAmount.
  2. Run grit migrate.
  3. Open GORM Studio and look at the invoices table. You'll see both columns.
  4. In notes.md, write the SQL you'd run to consolidate: copy data + drop old column.

What's next

Chapter 3 — Auth + RBAC. JWT, OAuth2, TOTP 2FA, and role-based gating. The heaviest chapter, the highest leverage.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub