Golang: GORM Database Seeding

Learn how to efficiently seed a MySQL database in Go using GORM, with a clear guide on migrations, best practices, and transactions.


1. Prerequisites

  1. Go installed (1.19+ recommended).
  2. MySQL server running locally or remotely.
  3. GORM library. You can install the necessary packages using: go get -u gorm.io/gorm go get -u gorm.io/driver/mysql
  4. A basic understanding of how GORM handles models, migrations, and connections.

2. Setting Up Your Project

Create a new folder for your project. Inside it, initialize a new Go module and install required dependencies:

mkdir gorm-seeding-tutorial
cd gorm-seeding-tutorial
go mod init github.com/yourusername/gorm-seeding-tutorial
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

Your folder structure might look like this:

gorm-seeding-tutorial/
│   go.mod
│   go.sum
└── main.go

3. Connecting to MySQL

You’ll need a MySQL DSN (Data Source Name) with the following structure:

username:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local

If you have MySQL running locally with a database named testdb, a typical DSN might look like:

dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"

Create a main.go file with the following content:

package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func main() {
    // Replace the DSN with your actual credentials
    dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"

    // 1. Connect to the database
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    fmt.Println("Database connection established!")
}

Running go run main.go should print Database connection established! if everything is configured correctly.


4. Defining Models

GORM uses structs to define models that map to database tables. Let’s create a User model with simple fields:

package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

// User model
type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string `gorm:"size:100;not null"`
    Email string `gorm:"size:100;uniqueIndex;not null"`
}

func main() {
    dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // 2. Auto-migrate the schema
    err = db.AutoMigrate(&User{})
    if err != nil {
        panic("failed to migrate database")
    }

    fmt.Println("Database migrated successfully!")
}

When you run this code, GORM will create (or update) the users table in testdb if it doesn’t already exist.


5. Seeding the Database

5.1 Seeding Using a Simple Insert

A quick way to seed data is to insert records directly in main.go (or a dedicated file) after migration.

package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

// User model
type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string
    Email string
}

func main() {
    dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // Auto-migrate the schema
    if err := db.AutoMigrate(&User{}); err != nil {
        panic("failed to migrate database")
    }

    // Seed the database
    seedData(db)

    fmt.Println("Database seeded successfully!")
}

// seedData is responsible for inserting the initial dataset
func seedData(db *gorm.DB) {
    // Check if the table has data
    var count int64
    db.Model(&User{}).Count(&count)

    if count == 0 { // Only seed if table is empty
        users := []User{
            {Name: "Alice", Email: "alice@example.com"},
            {Name: "Bob", Email: "bob@example.com"},
            {Name: "Charlie", Email: "charlie@example.com"},
        }

        if err := db.Create(&users).Error; err != nil {
            fmt.Println("Error seeding data:", err)
        } else {
            fmt.Println("Seeded user data successfully.")
        }
    } else {
        fmt.Println("Users table already has data. Skipping seeding.")
    }
}

Key Points:

  • We call db.Model(&User{}).Count(&count) to see if any rows exist. If none, we seed; if there are existing records, we skip.
  • Adjust the condition or remove it if you prefer always overwriting seed data.

5.2 Organizing Seeds in Separate Files

As your application grows, organizing seeding logic in separate files/directories becomes essential. For instance:

gorm-seeding-tutorial/
├── db
│   └── seed.go
├── models
│   └── user.go
└── main.go
  • models/user.go will hold your User struct.
  • db/seed.go will hold your seeding logic.
  • main.go will handle the high-level setup (connecting, migrating, then seeding).

Example db/seed.go:

package db

import (
    "fmt"

    "gorm.io/gorm"
    "github.com/yourusername/gorm-seeding-tutorial/models"
)

// Seed is the entry point for all your seeding logic
func Seed(db *gorm.DB) {
    seedUsers(db)
}

func seedUsers(db *gorm.DB) {
    var count int64
    db.Model(&models.User{}).Count(&count)

    if count == 0 {
        users := []models.User{
            {Name: "Alice", Email: "alice@example.com"},
            {Name: "Bob", Email: "bob@example.com"},
            {Name: "Charlie", Email: "charlie@example.com"},
        }
        if err := db.Create(&users).Error; err != nil {
            fmt.Println("Error seeding data:", err)
        } else {
            fmt.Println("Seeded user data successfully.")
        }
    } else {
        fmt.Println("Users table already has data. Skipping seeding.")
    }
}

Then in main.go:

package main

import (
    "fmt"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"

    "github.com/yourusername/gorm-seeding-tutorial/db"
    "github.com/yourusername/gorm-seeding-tutorial/models"
)

func main() {
    // 1. Connect to the database
    dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
    dbConn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // 2. Migrate schema
    if err := dbConn.AutoMigrate(&models.User{}); err != nil {
        panic("failed to migrate database")
    }
    fmt.Println("Database migrated successfully!")

    // 3. Seed database
    db.Seed(dbConn)
    fmt.Println("Database seeded successfully!")
}

6. Handling Complex Seeding Scenarios

6.1 Using Transactions for Seeding

If your seeding logic involves multiple tables or you want to ensure data consistency, you can use a transaction:

func seedData(db *gorm.DB) {
    txErr := db.Transaction(func(tx *gorm.DB) error {
        // Example: Insert a single user
        user := User{Name: "Alice", Email: "alice@example.com"}
        if err := tx.Create(&user).Error; err != nil {
            return err // returning an error will rollback the transaction
        }

        // Insert more data or do other operations here...

        // Return nil to commit the transaction
        return nil
    })

    if txErr != nil {
        fmt.Println("Error seeding data, transaction rolled back:", txErr)
    } else {
        fmt.Println("Successfully seeded data with transaction!")
    }
}

6.2 Clearing Existing Data

Sometimes you may want to delete existing data to start fresh:

func seedData(db *gorm.DB) {
    // Clear existing data
    if err := db.Exec("DELETE FROM users").Error; err != nil {
        fmt.Println("Error clearing data:", err)
        return
    }

    // Now insert fresh data
    newUser := User{Name: "Dave", Email: "dave@example.com"}
    if err := db.Create(&newUser).Error; err != nil {
        fmt.Println("Error seeding data:", err)
    }
}

7. Testing Your Seeds

  1. Run migrations and seeding: go run main.go
  2. Check your MySQL database to verify that the users table has been created and seeded. For example: mysql -u root -p USE testdb; SHOW TABLES; SELECT * FROM users;
  3. You should see rows corresponding to your seeded data.

8. Tips & Best Practices

  • Use separate environments: Keep development/test seeding scripts separate from production; you typically won’t seed production data this way.
  • Check for existing data (as shown) to avoid accidental duplication. Alternatively, you can clear data first if that suits your workflow.
  • Wrap complex seeding in transactions so that if any part fails, the entire seed is rolled back.
  • Organize seeding logic in dedicated files or packages as your project grows.
  • Seed in the correct order when dealing with relationships (e.g., create “parent” records before “child” records).

Conclusion

Seeding a MySQL database in Go with GORM is straightforward once you’ve set up the correct driver and DSN. By following this guide, you’ve learned how to:

  1. Connect to MySQL using GORM.
  2. Auto-migrate your models.
  3. Seed data (with optional checks).
  4. Organize seeding in separate files for larger projects.

Feel free to expand this approach for more complex scenarios, including multiple models, relationships, and environment-based seeding strategies.

Good luck with your Go + GORM + MySQL projects!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *