Seeders
Seeders fill your database with starter data — the admin account, demo users, sample catalogue rows, anything you want on a fresh install. In Grit, every resource gets its own seeder file, you can generate one in a single command, and --faker fills it with realistic rows (relationships included).
How it fits together
There is one thin Seed() runner that calls a Seed<Resource> function per resource. Each of those lives in its own file under internal/database/, so a seeder is always easy to find and edit — including the built-in users and blogs.
seed.go ← Seed(db): the runner│├─ SeedUsers(db) → users_seeder.go (admin + demo users)├─ SeedBlogs(db) → blogs_seeder.go (sample posts)├─ SeedCategories(db) → categories_seeder.go└─ SeedProducts(db) → products_seeder.go▲└─ grit generate seeder / --seed adds theseseed_helpers.go ← pickID / firstID (relationship helpers)
When you generate a seeder, Grit writes the <resource>_seeder.go file and registers its call in seed.go at the // grit:seeders marker. You never wire anything by hand.
Running seeders
After migrating, run every seeder with one command from anywhere in the project:
$grit seed
Seeders are idempotent — each checks whether its table already has rows and skips if so, so re-running never duplicates data.
Seeding database...Created admin user: admin@example.com / admin123Created user: jane@example.com / admin123Created blog: "Getting Started with Grit" (published)Seeded 8 categorySeeded 60 productDatabase seeded successfully.
Generating a seeder
Add a seeder to a resource you already generated — it reads the model to pre-fill one example record with the right field types:
$grit generate seeder Customer
Pass more than one, or emit the seeder at the same time you scaffold the resource with --seed:
$grit generate seeder Customer Order Product$grit generate resource Tag --fields "name:string" --seed
Filling rows with faker
Without a flag you get one editable example row. Add --faker (and --count N, default 10) to instead generate a seeder that fills many rows with gofakeit, inserted in batches. It ships inside the API, so this works offline.
$grit generate seeder Product --faker --count 60
Values are chosen from each field's name and type:
| Field | Faker value |
|---|---|
| name | gofakeit.Name() |
| gofakeit.Email() | |
| phone / city / company | gofakeit.Phone() / City() / Company() |
| float (price) | gofakeit.Price(1, 1000) |
| int / uint | gofakeit.Number(1, 100) |
| bool | gofakeit.Bool() |
| date / datetime | gofakeit.Date() |
| file:image / files:image | a sample picsum image URL |
Anything the guesser doesn't recognise falls back to gofakeit.Word(). A column marked unique seeds from the row's number instead (SKU-0000001, SKU-0000002), so it never collides however many rows you ask for. It's just Go: open the file and swap in your own calls.
Seeding a million rows
Give grit seed a resource and a count to top that table up to exactly that many rows:
$grit seed Contact --count 1000000
- It tops up. The seeder counts what is in the table and inserts only the rest. Run it twice and the second run does nothing. Stop it halfway and the next run carries on from where it stopped, to exactly the count you asked for.
- It inserts in batches. Rows go in with multi-row inserts, one transaction per batch, sized to what the database accepts for the table's column count (up to 1,000 rows). Model hooks still run for every row, so IDs, slugs and auto-numbers work as usual.
- Memory stays flat. Rows are built in chunks by a few goroutines and written as they are ready, never held all at once. A million rows used about 15 MB of heap.
- It fails loudly. Progress prints every two seconds with rows per second and time left, and the first batch that fails stops the run with an error, instead of logging and reporting success over a half-empty table.
Measured on one development machine, same resource (name, email, phone and a unique code), before and after this change:
| Rows | SQLite, before | SQLite, now | Postgres, before | Postgres, now |
|---|---|---|---|---|
| 10,000 | 96.6 s | 3.7 s | 31.9 s | 2.4 s |
| 1,000,000 | about 2 h 45 min | 3 min 14 s | about 53 min | about 50 s |
The before figures for a million rows are estimated from the measured rate; the after figures are measured. SQLite takes one writer at a time, so it is slower than Postgres, which writes three batches at once.
A seeder from before --count. Seeders generated by an older Grit still run with grit seed, but not with --count. Run grit upgrade, then regenerate the seeder with grit generate seeder Contact --faker to switch it over.
Relationships
This is the part most seeders get wrong. A belongs_to field (a Product's Category, say) needs a real parent id, not a random string. Grit handles it: the seeder loads the parent ids once and links each row to one of them — a random parent for faker, the first parent for the static example.
func SeedProductsTo(db *gorm.DB, target int64) error {// Link each row to an existing parent (loaded once).var categoryIDs []stringdb.Model(&models.Category{}).Pluck("id", &categoryIDs)return SeedTopUp(db, "products", SeedPlan[models.Product]{Target: target,Make: func(n int64) models.Product {return models.Product{Name: gofakeit.Name(),Price: gofakeit.Price(1, 1000),CategoryID: pickID(categoryIDs), // a real, existing category}},})}
Seed order matters. A child can only link to a parent that already exists, so seed parents first. The runner calls seeders in the order you generated the resources — generate Category before Product and you're set. Need a different order? Reorder the calls in seed.go.
Editing a seeder
A static seeder is a plain slice of model structs — edit the values, add rows, done:
func SeedCategories(db *gorm.DB) error {var count int64db.Model(&models.Category{}).Count(&count)if count > 0 {return nil // already seeded}records := []models.Category{{Name: "Sample Name"}, // ← edit these// {Name: "Phones"}, // ← or add your own// {Name: "Accessories"},}for _, r := range records {db.Create(&r)}return nil}
Command reference
| Command | Does |
|---|---|
| grit seed | Run every seeder |
| grit seed X --count N | Top X up to N rows, in batches; resumes if stopped |
| grit generate seeder X [Y…] | Add a seeder to existing resource(s) |
| grit generate resource X … --seed | Emit the seeder while scaffolding |
| … --faker --count N | Fill N rows with gofakeit instead of one example |
