Migrations
Nimbus provides a structured migration system through database.Migrator. Each migration has a Name, an Up function to apply changes, and a Down function to reverse them. Migrations run in alphabetical order by name, so prefix them with a timestamp.
Creating a migration
Generate a migration scaffold with the CLI:
nimbus make:migration create_posts
This creates a file in database/migrations/ with the migration struct pre-filled. The generated name is timestamped (e.g. 20260308120000_create_posts) so migrations always run in order.
Migration struct
Each migration is a database.Migration with transaction-aware settings:
var CreatePosts = database.Migration{
Name: "20260308120000_create_posts",
Up: func(db *gorm.DB) error {
return db.Exec(`CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT,
user_id INTEGER REFERENCES users(id),
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME
)`).Error
},
Down: func(db *gorm.DB) error {
return db.Exec("DROP TABLE IF EXISTS posts").Error
},
// Set true only when your DDL cannot run in a transaction.
NonTransactional: false,
}
Nimbus wraps supported migrations in a transaction by default (Postgres, SQLite). Use NonTransactional: true only for dialect/DDL operations that require it.
Running migrations
Create a Migrator with your database connection and a slice of migrations, then call Up():
migrator := database.NewMigrator(db, []database.Migration{
migrations.CreateUsers,
migrations.CreatePosts,
migrations.CreateComments,
})
if err := migrator.Up(); err != nil {
log.Fatalf("migration failed: %v", err)
}
Rolling back
Call Down() to reverse the last migration. This executes the Down function of the most recently applied migration:
if err := migrator.Down(); err != nil {
log.Fatalf("rollback failed: %v", err)
}
AutoMigrate with GORM
For rapid prototyping you can use GORM's AutoMigrate directly. This creates or alters tables to match your struct definitions but does not support dropping columns or rollbacks:
db.AutoMigrate(&User{}, &Post{}, &Comment{})
Best practices
- Always write both
UpandDownfunctions so rollbacks work correctly. - Use timestamp prefixes in migration names to guarantee ordering.
- Keep migrations small and focused — one table or one change per migration.
- Never edit a migration that has already been applied in production. Create a new migration instead.
- Use
AutoMigrateonly in development. Prefer explicit migrations for production deployments. - Test migrations against a fresh database and verify rollbacks before deploying.