Running Seeders
Learn how to register and run seeders using runner.RunAll() and runner.Run() methods.
Running Seeders
Once you've defined your seeders, use the seeder runner to register and execute them.
Auto-Registration with init()
Similar to migrations, seeders can be auto-registered from init() functions using seeder.AutoRegister(). This is the recommended approach when using the CLI entry point app.Run().
package seeders
import (
"database/sql"
"github.com/gopackx/go-migration/pkg/seeder"
)
func init() {
seeder.AutoRegister("UserSeeder", &UserSeeder{})
}
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
}When using app.Run(), auto-registered seeders are discovered automatically — no manual registration needed. Just blank-import the seeders package in your main.go:
package main
import (
"github.com/gopackx/go-migration/pkg/cli/app"
_ "github.com/lib/pq"
_ "your-project/migrations"
_ "your-project/seeders" // triggers init() auto-registration
)
func main() {
app.Run()
}The CLI's db:seed, db:seed:rollback, and db:seed:truncate commands hold the cross-process migration lock while they run, so seeders never run concurrently with each other or with migrations. They accept --lock-timeout and --no-lock.
seeder.AutoRegister() panics on duplicate names and on empty/whitespace-only names, just like migrator.AutoRegister(). This ensures configuration mistakes are caught at startup.
Manual Registration
For more control, build a Registry, register your seeders on it, then hand it to a Runner:
package main
import (
"database/sql"
"log"
_ "github.com/lib/pq"
"github.com/gopackx/go-migration/pkg/seeder"
"your-project/seeders"
)
func main() {
db, err := sql.Open("postgres", "postgres://user:password@localhost:5432/mydb?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
reg := seeder.NewRegistry()
if err := reg.Register("UserSeeder", &seeders.UserSeeder{}); err != nil {
log.Fatal(err)
}
if err := reg.Register("PostSeeder", &seeders.PostSeeder{}); err != nil {
log.Fatal(err)
}
if err := reg.Register("RoleSeeder", &seeders.RoleSeeder{}); err != nil {
log.Fatal(err)
}
// The logger may be nil to skip logging.
runner := seeder.NewRunner(reg, db, nil)
// Run all seeders
if err := runner.RunAll(); err != nil {
log.Fatal(err)
}
log.Println("All seeders completed successfully!")
}Runner Options
seeder.NewRunner accepts optional RunnerOptions after the logger:
func NewRunner(registry *Registry, db *sql.DB, logger Logger, opts ...RunnerOption) *Runnerseeder.WithDialect(d) sets the SQL dialect the runner uses for the statements it generates itself — currently the identifier quoting in runner.Truncate(). The default is seeder.DialectPostgres; pass the dialect that matches your database:
runner := seeder.NewRunner(reg, db, nil, seeder.WithDialect(seeder.DialectMySQL))| Database | Option |
|---|---|
| PostgreSQL | seeder.WithDialect(seeder.DialectPostgres) (default) |
| MySQL | seeder.WithDialect(seeder.DialectMySQL) |
| SQLite | seeder.WithDialect(seeder.DialectSQLite) |
The CLI picks the dialect from the connection's driver automatically.
A Runner created in your own code does not take the migration lock. If seeders may run while another process migrates, wrap the call in m.Lock(ctx) — see Locking Your Own Work.
Registry.Register() returns an error — it wraps seeder.ErrInvalidSeederName for empty names and seeder.ErrDuplicateSeeder for duplicates. Unlike AutoRegister(), it does not panic.
Running All Seeders
runner.RunAll() executes every registered seeder:
if err := runner.RunAll(); err != nil {
log.Fatal(err)
}Execution Order
Without dependencies, seeders run in a deterministic order (sorted by name). Dependencies override this so that a seeder always runs after the seeders it depends on.
Sequential Execution
Each seeder's Run method is called one at a time. If a seeder returns an error, execution stops immediately.
Error Handling
RunAll() returns the first error encountered. Seeders that completed before the failure remain applied.
Running a Specific Seeder
Use runner.Run("SeederName") to execute a single seeder by its registered name:
// Run only the UserSeeder
if err := runner.Run("UserSeeder"); err != nil {
log.Fatal(err)
}This is useful when you need to re-seed a specific table without running all seeders again.
Complete Example
package main
import (
"database/sql"
"log"
"os"
_ "github.com/lib/pq"
"github.com/gopackx/go-migration/pkg/seeder"
"your-project/seeders"
)
func main() {
db, err := sql.Open("postgres", "postgres://user:password@localhost:5432/mydb?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
reg := seeder.NewRegistry()
for name, s := range map[string]seeder.Seeder{
"UserSeeder": &seeders.UserSeeder{},
"RoleSeeder": &seeders.RoleSeeder{},
"PostSeeder": &seeders.PostSeeder{},
} {
if err := reg.Register(name, s); err != nil {
log.Fatal(err)
}
}
runner := seeder.NewRunner(reg, db, nil)
// Check if a specific seeder was requested
if len(os.Args) > 1 {
name := os.Args[1]
log.Printf("Running seeder: %s", name)
if err := runner.Run(name); err != nil {
log.Fatal(err)
}
} else {
log.Println("Running all seeders...")
if err := runner.RunAll(); err != nil {
log.Fatal(err)
}
}
log.Println("Seeding complete!")
}The name passed to runner.Run() must match the name used in reg.Register() exactly. If the name doesn't match any registered seeder, seeder.ErrSeederNotFound is returned.
What's Next?
- Dependencies — control the execution order of seeders with dependency declarations