Migrations & Seeding
Database migrations with GORM AutoMigrate and seeding with initial data.
Overview
Grit uses GORM AutoMigrate for database schema management. Migrations run automatically on API startup and can also be triggered manually via the CLI. Seeding populates the database with initial data for development and production.
Migrations
How It Works
GORM AutoMigrate creates tables, adds missing columns, and creates indexes based on your Go model definitions. It does not delete columns or drop tables — it's additive only.
All models are registered in apps/api/internal/models/user.go via the AutoMigrate function:
func AutoMigrate(db *gorm.DB) error {
models := []interface{}{
&User{},
&Upload{},
// grit:models <-- new models injected here by grit generate
}
for _, model := range models {
if err := db.AutoMigrate(model); err != nil {
log.Printf("Warning: migration error for %T: %v (skipping)", model, err)
}
}
return nil
}Running Migrations
Migrations run automatically when the API starts. You can also run them manually:
# Run migrations
grit migrate
# Drop all tables and re-migrate (development only!)
grit migrate --freshFresh Migrations
The --fresh flag drops all tables in the database and re-creates them from scratch. This is useful during development when you've made breaking schema changes.
Warning: This destroys all data. Never use --fresh in production.
# Reset everything and re-seed
grit migrate --fresh
grit seedAdding New Models
When you run grit generate resource, it automatically:
- Creates a new Go model file
- Injects the model into AutoMigrate via the // grit:models marker
- Migrations run on next API startup or grit migrate
To add a model manually:
// apps/api/internal/models/post.go
type Post struct {
ID uint `gorm:"primarykey" json:"id"`
Title string `gorm:"size:255;not null" json:"title"`
Content string `gorm:"type:text" json:"content"`
Published bool `gorm:"default:false" json:"published"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}Then add it to AutoMigrate in models/user.go above the // grit:models marker.
Seeding
Running Seeders
grit seedThis creates:
| Account | Password | Role | |
|---|---|---|---|
| Admin | admin@example.com | admin123 | ADMIN |
| Jane Cooper | jane@example.com | admin123 | EDITOR |
| Robert Fox | robert@example.com | admin123 | USER |
| Emily Davis | emily@example.com | admin123 | USER |
| Michael Chen | michael@example.com | admin123 | USER (inactive) |
How Seeders Work
Seeders live in apps/api/internal/database/seed.go. The main Seed function calls individual seeder functions:
func Seed(db *gorm.DB) error {
if err := seedAdminUser(db); err != nil {
return err
}
if err := seedDemoUsers(db); err != nil {
return err
}
// grit:seeders <-- add your own seeders above this marker
return nil
}Adding Custom Seeders
Add new seeder functions in seed.go:
func seedCategories(db *gorm.DB) error {
categories := []models.Category{
{Name: "Technology", Slug: "technology"},
{Name: "Business", Slug: "business"},
{Name: "Design", Slug: "design"},
}
for _, c := range categories {
var count int64
db.Model(&models.Category{}).Where("slug = ?", c.Slug).Count(&count)
if count > 0 {
continue
}
if err := db.Create(&c).Error; err != nil {
return err
}
}
return nil
}Then call it from the Seed function:
if err := seedCategories(db); err != nil {
return fmt.Errorf("seeding categories: %w", err)
}Idempotent Seeders
Seeders check if records already exist before creating them. This means you can run grit seed multiple times without creating duplicates.
Common Workflow
# Initial setup
docker compose up -d # Start PostgreSQL
grit migrate # Create tables
grit seed # Populate with test data
# After adding a new resource
grit generate resource Post --fields "title:string,content:text"
grit migrate # Apply new table
# Reset during development
grit migrate --fresh # Drop everything
grit seed # Re-populate