gormigrate
repository·master·Indexed 22 days ago
https://github.com/go-gormigrate/gormigrateA minimalistic migration helper for Gorm that adds schema versioning and rollback support. It allows developers to define migrations with unique IDs, apply changes via Migrate functions, and undo them via Rollback functions. It supports any Gorm-compatible database, including MySQL, PostgreSQL, SQLite, and SQL Server, and provides utilities like InitSchema for bulk initialization of empty databases.
What's inside gormigrate
- Gormigrate does not include a built-in locking mechanism. If you are running migrations automatically in a distributed setup (e.g., multiple instances of your application running simultaneously), you must implement a distributed lock/mutex mechanism (such as using Redis) to prevent race conditions during the migration process.
Run migrations with Gormigrate
masterGormigrate provides a minimalistic way to handle schema versioning and rollbacks for Gorm. You initialize a migration manager using
gormigrate.New, passing your*gorm.DBinstance, an options struct, and a slice of*gormigrate.Migrationobjects. Each migration requires a uniqueID, aMigratefunction to apply changes, and aRollbackfunction to undo them.Best Practice: Define your model structs inside the
MigrateandRollbackfunctions. This prevents side effects if your global application structs change over time, which would otherwise break old migrations.package main import ( "log" "github.com/go-gormigrate/gormigrate/v2" "github.com/google/uuid" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func main() { db, err := gorm.Open(sqlite.Open("./data.db"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) if err != nil { log.Fatal(err) } m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{{ // create `users` table ID: "201608301400", Migrate: func(tx *gorm.DB) error { type user struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;uniqueIndex"` Name string } return tx.Migrator().CreateTable(&user{}) }, Rollback: func(tx *gorm.DB) error { return tx.Migrator().DropTable("users") }, }}, { // add `age` column to `users` table ID: "201608301415", Migrate: func(tx *gorm.DB) error { type user struct { Age int } return tx.Migrator().AddColumn(&user{}, "Age") }, Rollback: func(tx *gorm.DB) error { type user struct { Age int } return tx.Migrator().DropColumn(&user{}, "Age") }, }}) if err := m.Migrate(); err != nil { log.Fatalf("Migration failed: %v", err) } log.Println("Migration did run successfully") }Install Gormigrate for Gorm v2
masterTo use Gormigrate with Gorm v2, import the package using the following path:
github.com/go-gormigrate/gormigrate/v2If you are using the older Gorm v1 (which uses
github.com/jinzhu/gorm), you must use thegopkg.in/gormigrate.v1import path instead.import "github.com/go-gormigrate/gormigrate/v2"Set up integration testing environments with Docker Compose
masterThe
integration-test/docker-compose.ymlfile provides a pre-configured environment for running integration tests against multiple supported databases. You can use this file to spin up local instances of PostgreSQL, MySQL, MariaDB, and Microsoft SQL Server to verify your migrations.services: postgres: image: postgres:18-alpine ports: - 5432:5432 environment: POSTGRES_DB: gormigrate POSTGRES_USER: gormigrate POSTGRES_PASSWORD: gormigrate mysql: image: mysql:9 ports: - 3306:3306 environment: MYSQL_DATABASE: gormigrate MYSQL_ROOT_PASSWORD: gormigrate MYSQL_USER: gormigrate MYSQL_PASSWORD: gormigrate mariadb: image: mariadb:12 ports: - 3307:3306 environment: MARIADB_DATABASE: gormigrate MARIADB_ROOT_PASSWORD: gormigrate MARIADB_USER: gormigrate MARIADB_PASSWORD: gormigrate sqlserver: image: mcr.microsoft.com/mssql/server:2025-latest ports: - 1433:1433 environment: ACCEPT_EULA: Y MSSQL_SA_PASSWORD: LoremIpsum86Initialize a clean schema using InitSchema
masterWhen deploying to a new, empty database, running a long list of individual migrations can be inefficient. You can use
m.InitSchema(func(tx *gorm.DB) error { ... })to define a single function that runs only if no previous migrations have been recorded in the database. This is ideal for performing a bulkAutoMigrateor setting up complex constraints and foreign keys in one step.m.InitSchema(func(tx *gorm.DB) error { err := tx.AutoMigrate( &Organization{}, &User{}, ) if err != nil { return err } if err := tx.Exec("ALTER TABLE users ADD CONSTRAINT fk_users_organizations FOREIGN KEY (organization_id) REFERENCES organizations (id)").Error; err != nil { return err } return nil })Configure Gormigrate with Options
masterUse the
Optionsstruct to customize how migrations are stored and executed. If you don't provide options,DefaultOptionsis used.Field Type Description TableNamestringThe name of the table where migration IDs are stored (default: "migrations")IDColumnNamestringThe name of the column storing the migration ID (default: "id")IDColumnSizeintThe length of the ID column (default: 255)UseTransactionboolIf true, executes migrations inside a single transaction (Note: not all DBs support DDL in transactions)ValidateUnknownMigrationsboolIf true,Migrate()fails if IDs exist in the database that are not present in your code.options := &gormigrate.Options{ TableName: "my_migrations", UseTransaction: true, }Configure Gormigrate via Options
masterYou can customize the behavior of the migration manager by providing a custom
Optionsstruct togormigrate.New.type Options struct { // TableName is the migration table. TableName string // IDColumnName is the name of column where the migration id will be stored. IDColumnName string // IDColumnSize is the length of the migration id column IDColumnSize int // UseTransaction makes Gormigrate execute migrations inside a single transaction. // Keep in mind that not all databases support DDL commands inside transactions. UseTransaction bool // ValidateUnknownMigrations will cause migrate to fail if there's unknown migration // IDs in the database ValidateUnknownMigrations bool }Supported databases for Gormigrate
masterGormigrate supports any database that is compatible with Gorm, including:
- MySQL
- MariaDB
- PostgreSQL
- SQLite
- Microsoft SQL Server
- TiDB
- Clickhouse
Initialize a clean schema with InitSchema()
masterIf you are working with a completely empty database, you can use
InitSchema(fn)to define a function that runs once to set up the initial state (e.g., creating all base tables and foreign keys).InitSchemais only executed if no migrations have been recorded in the migration table yet. This prevents running a full migration suite on an existing database that was already initialized.g.InitSchema(func(tx *gorm.DB) error { return tx.AutoMigrate(&User{}, &Account{}) }) // When Migrate() is called on a fresh DB, it runs InitSchema first err := g.Migrate()Initialize Gormigrate with New()
masterTo use Gormigrate, call
New()with a*gorm.DBinstance, an optional*Optionspointer, and a slice of*Migrationobjects. Ifoptionsisnil, it defaults toDefaultOptions.import ( "gorm.io/gorm" "github.com/go-gormigrate/go-gormigrate/gormigrate" ) // ... setup db ... migrations := []*gormigrate.Migration{ { ID: "20230101000000", Migrate: func(tx *gorm.DB) error { return tx.AutoMigrate(&User{}) }, }, } g := gormigrate.New(db, nil, migrations)Rollback migrations with RollbackLast() and RollbackTo()
masterTo undo changes, use
RollbackLast()to undo the single most recent migration, orRollbackTo(migrationID)to undo all migrations occurring after the specifiedmigrationID(the target ID itself is NOT rolled back).Note: A migration can only be rolled back if a
Rollbackfunction was provided in its definition. If missing, it returnsErrRollbackImpossible.// Undo the very last migration that was run err := g.RollbackLast() // Undo migrations until we reach this ID err := g.RollbackTo("20230101000000")Run migrations with Migrate() and MigrateTo()
masterUse
Migrate()to execute all pending migrations in the order they are defined. UseMigrateTo(migrationID)to execute all pending migrations up to and including the specifiedmigrationID.// Run all pending migrations err := g.Migrate() // Run migrations up to a specific ID err := g.MigrateTo("202301021504")