Skip to main content

Simple Golang database seeding abstraction for Gorm

·3 mins

One of the most feature-full ORMs for Go that I have worked with is Gorm. If you would like to learn more about it, I recommend checking out its official website and its documentation.

Person looking at hovering files graphic

Recenlty I wanted to write a small database seeding abstraction. Database seeding is a process in which an initial set of data is provided to a database when it is being set up or installed.

Yet, in true Go fashion my goal was to keep the abstraction tiny, yet provide some structure to folks that would write seeds in this application. This is absolutely not the only way you can achieve this, the options are plenty. Still, this is one approach that I have found to be working well.

We will have three different components:

  1. Create a Seed abstraction.
  2. Identify path for the seeds and how to collect them in a single seeds package.
  3. Create a seeder command line tool that can run the seeds against a database.

First, to define a small Seed type that would be coupled to the gorm.DB connection instance:

// pkg/seed/seed.go
package seed

import (
	"github.com/jinzhu/gorm"
)

type Seed struct {
	Name string
	Run  func(*gorm.DB) error
}

Next, we need to define a path where we’ll store the seeds and how to collect them:

// pkg/seeds/seeds.go
package seeds

import (
	"github.com/fteem/seeding/pkg/seed"
)

func All() []seed.Seed {
	return []seed.Seed{}
}

The seeds package will contain all instances of seed.Seed and collect them in the All function. This will allow the third component, a CLI tool, to iterate over them and execute them against a database. Additionally, in the same seeds package we can have separate files for seeding various types of data.

For example:

// pkg/seeds/users.go
package seeds

func CreateUser(db *gorm.DB, name string, age int) error {
        return db.Create(&users.User{Name: name, Age: age}).Error
}

The usage of the function in All:

// pkg/seeds/seeds.go
import (
	"github.com/fteem/seeding/pkg/seed"
)

func All() []seed.Seed {
	return []seed.Seed{
        	seed.Seed{
                        Name: "CreateJane",
                        Run: func(db *gorm.DB) error {
                              CreateUser(db, "Jane", 30)
                        },
                },
        	seed.Seed{
                        Name: "CreateJohn",
                        Run: func(db *gorm.DB) error {
                              CreateUser(db, "John", 30)
                        },
                },
        }
}

The example above is contrived, but it can work really well if you have to compose seeds using multiple such functions.

Last, a very simple command line tool that can running the seeds we defined:

// pkg/seeder/seeder.go
package main

import (
	"log"

	"github.com/fteem/seeding/pkg/seeds"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
	dbConn := openConnection()
	defer dbConn.Close()

	for _, seed := range seeds.All() {
		if err := seed.Run(dbConn); err != nil {
			log.Fatalf("Running seed '%s', failed with error: %s", seed.Name, err)
		}
	}
}

func openConnection() *gorm.DB {
        db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
	if err != nil {
		log.Fatalf("Couldn't establish database connection: %s", err)
	}
	return db
}

The binary produced by this is quite a simple one: loads all of the seeds using the seeds.All function, iterates over the collection and executes their Run function. If an error occurs, the whole program bombs.

That’s it. Very thin and simple way to abstract away your database seeds using Gorm. Before you go, let me know in the comments how you approach database seeding.