How to Seed Initial Data into the Database with GORM in golang-clean-architecture
Seeding initial data into the database with GORM in a Clean Architecture project involves creating a standalone command in cmd/seed/main.go that loads configuration, initializes a GORM database connection via datastore.NewDB(), and persists domain entities using db.Create().
The manakuro/golang-clean-architecture repository demonstrates how to implement database seeding while maintaining strict separation of concerns across Clean Architecture layers. This approach keeps your seeding logic isolated from business logic and ensures your database initializes with required reference data every time you spin up a new environment.
Understanding the Clean Architecture Layers for Database Seeding
The repository organizes code into distinct layers that collaborate to seed initial data into the database with GORM:
- Configuration (
pkg/config): Theconfig.ReadConfig()function parses environment variables intoconfig.C, providing database credentials and connection parameters. - Domain (
pkg/domain/model): Defines core entities like theUserstruct inpkg/domain/model/user.go, which maps directly to database tables via GORM tags. - Infrastructure (
pkg/infrastructure/datastore): Containsdatastore.NewDB()inpkg/infrastructure/datastore/db.go, which constructs the MySQL DSN and returns an initialized*gorm.DBinstance. - Entry Points (
cmd/seed): The standalonemain.gofile incmd/seed/orchestrates the seeding process without coupling to the main application server.
Step-by-Step Guide to Seeding Initial Data with GORM
Load Application Configuration
Before connecting to the database, invoke config.ReadConfig() to populate the global configuration struct. This ensures the seeder uses the same database credentials as your main application.
config.ReadConfig()
Initialize the GORM Database Connection
Call datastore.NewDB() to build the connection. This function reads config.C.Database parameters (host, port, user, password, database name) and returns a configured *gorm.DB pointer.
db := datastore.NewDB()
defer db.Close()
Enable SQL logging during development by calling db.LogMode(true) immediately after initialization.
Define Domain Entities
Instantiate structs from the domain layer. For example, create a User value defined in pkg/domain/model/user.go:
user := &model.User{
ID: 1,
Name: "Tom",
Age: "20",
}
GORM automatically manages created_at and updated_at timestamps when these fields exist on the struct.
Persist Data Using GORM
Insert the record using db.Create(). This method generates and executes the appropriate SQL INSERT statement.
if err := db.Create(user).Error; err != nil {
log.Fatalf("failed to seed user data: %v", err)
}
For bulk operations, pass a slice of structs to Create():
users := []model.User{
{ID: 1, Name: "Tom", Age: "20"},
{ID: 2, Name: "Anna", Age: "25"},
{ID: 3, Name: "Mike", Age: "30"},
}
if err := db.Create(&users).Error; err != nil {
log.Fatalf("bulk seed failed: %v", err)
}
Complete Seeding Implementation Example
The following implementation from cmd/seed/main.go demonstrates the complete workflow for seeding initial data into the database with GORM:
package main
import (
"log"
"golang-clean-architecture/pkg/config"
"golang-clean-architecture/pkg/domain/model"
"golang-clean-architecture/pkg/infrastructure/datastore"
)
func main() {
// 1️⃣ Load configuration
config.ReadConfig()
// 2️⃣ Initialise GORM DB (MySQL)
db := datastore.NewDB()
db.LogMode(true) // optional: enable SQL logging
defer db.Close()
// 3️⃣ Define a user to seed
user := &model.User{
ID: 1,
Name: "Tom",
Age: "20",
// timestamps are left nil – GORM will fill them automatically
}
// 4️⃣ Persist the user
if err := db.Create(user).Error; err != nil {
log.Fatalf("failed to seed user data: %v", err)
}
}
Run this seeder independently without starting the main application server:
go run ./cmd/seed
Key Files and Their Roles in the Seeding Process
| Layer | File | Role |
|---|---|---|
| Entry point | cmd/seed/main.go |
Orchestrates config loading, DB creation, and data insertion. |
| Infrastructure | pkg/infrastructure/datastore/db.go |
Builds the GORM DB connection from config values via NewDB(). |
| Domain model | pkg/domain/model/user.go |
Defines the User entity mapped to the users table. |
| Configuration | pkg/config/config.go |
Holds DB credentials and other settings used by NewDB(). |
Summary
- Seeding initial data into the database with GORM requires a standalone entry point in
cmd/seed/main.gothat operates independently from your main application. - The
datastore.NewDB()function inpkg/infrastructure/datastore/db.gocentralizes database connection logic, ensuring seeders and the main app use identical connection parameters. - Use
db.Create()to insert single records or bulk slices; GORM automatically handles timestamp fields when defined in your domain models. - Keeping seeding logic in a separate
mainpackage undercmd/seedmaintains Clean Architecture principles by isolating infrastructure concerns from domain logic.
Frequently Asked Questions
How do I run the database seeder independently from the main application?
Execute go run ./cmd/seed from the project root. This compiles and runs only the seeding logic in cmd/seed/main.go without starting the main HTTP server or other application services, making it ideal for CI/CD pipelines or local development setup.
What is the role of the datastore.NewDB() function in the seeding process?
datastore.NewDB() acts as a factory that constructs a *gorm.DB instance using credentials from config.C.Database. Located in pkg/infrastructure/datastore/db.go, this function ensures the seeder connects to MySQL with the same DSN format and connection pooling settings used by the production application.
Can I seed multiple tables or bulk data using this approach?
Yes. Pass a slice of structs to db.Create(&slice) instead of a single pointer. For example, define users := []model.User{{...}, {...}} and call db.Create(&users) to insert all records in a single transaction. You can extend cmd/seed/main.go to seed multiple tables by repeating the create logic for each domain model.
How does Clean Architecture benefit database seeding compared to traditional approaches?
Clean Architecture isolates seeding logic in cmd/seed/main.go, keeping it separate from business logic in pkg/domain and infrastructure details in pkg/infrastructure. This separation means you can modify seed data without touching core application code, swap database drivers by changing only datastore.NewDB(), and test seeding logic independently using mock configurations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →