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): The config.ReadConfig() function parses environment variables into config.C, providing database credentials and connection parameters.
  • Domain (pkg/domain/model): Defines core entities like the User struct in pkg/domain/model/user.go, which maps directly to database tables via GORM tags.
  • Infrastructure (pkg/infrastructure/datastore): Contains datastore.NewDB() in pkg/infrastructure/datastore/db.go, which constructs the MySQL DSN and returns an initialized *gorm.DB instance.
  • Entry Points (cmd/seed): The standalone main.go file in cmd/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.go that operates independently from your main application.
  • The datastore.NewDB() function in pkg/infrastructure/datastore/db.go centralizes 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 main package under cmd/seed maintains 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →