When developing a database schema, it’s useful to represent it as a series of incremental changes that handle data migrations alongside the schema migrations. Goose is a great tool for this when working with SQL databases with its simplicity and first-class Go integration.

Goose migrations are stored in a directory either by date or with a sequence number, and can be created with the create command. Within the migration, the block annotated with the +goose Up comment is executed to apply the migration, and the block under +goose Down is run to roll it back. A basic migration might look like this:

-- +goose Up
CREATE TABLE test (
	name varchar PRIMARY KEY
);

-- +goose Down
DROP TABLE test;

Go Integration

Integrating goose with a Go application can streamline the process of managing database migrations by allowing distribution of the migrations directly with the binary via Go’s embedded filesystems. Additionally, since the database object is passed directly to goose when using the package it’s easy to reuse database connection parameters.

The following example package exports functions to apply, roll back, and view database migrations embedded from the migrations directory.

package migrate

import (
	"database/sql"
	"embed"

	"github.com/pressly/goose/v3"
)

//go:embed migrations/*.sql
var migrations embed.FS

func initGoose() error {
	goose.SetBaseFS(migrations)
	return goose.SetDialect("postgres")
}

func Up(db *sql.DB) error {
	if err := initGoose(); err != nil {
		return err
	}
	return goose.Up(db, "migrations")
}

func Down(db *sql.DB) error {
	if err := initGoose(); err != nil {
		return err
	}
	return goose.Down(db, "migrations")
}

func Status(db *sql.DB) error {
	if err := initGoose(); err != nil {
		return err
	}
	return goose.Status(db, "migrations")
}

To see what else the goose package provides, see its documentation.