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 Up and Down functions 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 AutoMigrate only in development. Prefer explicit migrations for production deployments.
  • Test migrations against a fresh database and verify rollbacks before deploying.