Project Structure
Recommended directory layout for projects using go-migration.
Project Structure
go-migration doesn't enforce a specific directory layout, but the following structure works well for most projects.
Recommended Layout
my-project/
├── cmd/
│ └── migrator/
│ └── main.go # CLI entry point (generated by go-migration init)
├── database/
│ ├── migrations/
│ │ ├── 2025_01_15_143022_1234_create_users_table.go
│ │ ├── 2025_01_15_143025_5678_create_posts_table.go
│ │ └── 2025_02_01_091500_2345_add_avatar_to_users.go
│ ├── seeders/
│ │ ├── user_seeder.go
│ │ ├── post_seeder.go
│ │ └── registry.go # Central seeder registration
│ └── factories/
│ ├── user_factory.go
│ └── post_factory.go
├── go.mod
├── go.sum
├── migration.json # Database configuration (JSON format)Migration Files
Each migration lives in its own file inside the database/migrations/ directory. Files use the naming format YYYY_MM_DD_HHMMSS_RRRR_description.go, where RRRR is a random 4-digit number to prevent timestamp collisions:
database/migrations/
├── 2025_01_15_143022_1234_create_users_table.go
├── 2025_01_15_143025_5678_create_posts_table.go
└── 2025_02_01_091500_2345_add_avatar_to_users.goThe old naming format (YYYYMMDDHHMMSS_description.go) is still supported for backward compatibility. You can mix both formats in the same project.
Each file defines a single struct implementing the migration interface, with an init() function for auto-registration:
package migrations
import (
"github.com/gopackx/go-migration/migrator"
"github.com/gopackx/go-migration/schema"
)
func init() {
migrator.AutoRegister("2025_01_15_143022_1234_create_users_table", &CreateUsersTable{})
}
type CreateUsersTable struct{}
func (m *CreateUsersTable) Up(s *schema.Builder) error {
return s.Create("users", func(bp *schema.Blueprint) {
bp.ID()
bp.String("name", 255)
bp.String("email", 255).Unique()
bp.Timestamps()
})
}
func (m *CreateUsersTable) Down(s *schema.Builder) error {
return s.Drop("users")
}Auto-Discovery Setup
With auto-discovery, you don't need a registry.go file. Each migration registers itself via init(), and the migrator loads them with a blank import. The recommended approach (generated by go-migration init) uses migrator.Run():
package main
import (
"github.com/gopackx/go-migration/migrator"
_ "your-module/database/migrations"
_ "your-module/database/seeders"
_ "github.com/lib/pq" // PostgreSQL driver
)
func main() {
migrator.Run()
}Auto-discovery eliminates the need for a central registration file. Adding a new migration is as simple as creating a new file with an init() function — no other files need to change.
Seeder Files
Seeders follow the same pattern — one file per seeder, with a registry for central registration:
package seeders
import "database/sql"
type UserSeeder struct{}
func (s *UserSeeder) Run(db *sql.DB) error {
_, err := db.Exec(`INSERT INTO users (name, email) VALUES ($1, $2)`, "Alice", "alice@example.com")
return err
}Factory Files
Factories define how to generate test data for your models:
package factories
import "github.com/gopackx/go-migration/seeder/factory"
type User struct {
ID int
Name string
Email string
}
func NewUserFactory() *factory.Factory[User] {
return factory.NewFactory(func(f factory.Faker) User {
return User{
Name: f.Name(),
Email: f.Email(),
}
})
}This layout is a recommendation, not a requirement. go-migration works with any directory structure — organize your files in whatever way makes sense for your project.
What's Next?
- Migrations — learn how to define and manage migrations
- Seeders — populate your database with test data
- Factories — generate realistic test data with factories