Learn how to efficiently seed a MySQL database in Go using GORM, with a clear guide on migrations, best practices, and transactions.
1. Prerequisites
- Go installed (1.19+ recommended).
- MySQL server running locally or remotely.
- GORM library. You can install the necessary packages using:
go get -u gorm.io/gorm go get -u gorm.io/driver/mysql
- 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 yourUser
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
- Run migrations and seeding:
go run main.go
- 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;
- 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:
- Connect to MySQL using GORM.
- Auto-migrate your models.
- Seed data (with optional checks).
- 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!
Leave a Reply