GORM and Goose Migrations
Tags: go, gorm, goose
Published: 2024-05-12 10:02:31

Let's get started with handling migrations in Go with [GORM](https://gorm.io/index.html) and [Goose](https://pressly.github.io/goose/).

**Prerequisites**
* Go installed
* GORM Supported SQL Database(I'll be using `Postgresql`)

**Note:**
You may refer to this [repo](https://github.com/KengoWada/gorm-goose) for the file structure.

### Install Goose Locally
Follow the steps [here](https://pressly.github.io/goose/installation/) to install Goose for your operating system.

I tried using the installation script with `Ubuntu 20.04` but I run into errors and found a solution [here](https://github.com/golang-migrate/migrate/issues/928#issuecomment-1570906452)
```sh
wget http://github.com/golang-migrate/migrate/releases/latest/download/migrate.linux-amd64.deb
sudo dpkg -i migrate.linux-amd64.deb

goose --help // To test if install went well.
```

### Start Dummy Project
* Create a directory
```sh
mkdir app
cd app
```
* Initialize it as a Go project
```sh
go mod init app
```
* Create `main.go` in the root of your project
```go
package main

func main() {

}
```

### Getting Started With GORM
* Create database directory
```sh
mkdir database
cd database
touch postgres.go
```
* Add the following to `database/postgres.go`
```go
package database

import (
    "database/sql"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

var dbURL := os.Getenv("GOOSE_DBSTRING")

var GORM_DB *gorm.DB
var SQL_DB *sql.DB
var DB_MIGRATOR gorm.Migrator

func ConnectToDatabase() (error) {
    db, err := gorm.Open(postgres.Open(dbURL), &gorm.Config{})
    if err == nil {
        GORM_DB = db
        SQL_DB, _ = db.DB()
        DB_MIGRATOR = db.Migrator()
    }
    return err
}
```
* At this point you'll have failed import errors, so let's fix that
```sh
go mod tidy
```
* Now create `env` in your projects root directory
```sh
mkdir env
touch env/goose.env
```
* Add the following to `env/goose.env`
```env
export GOOSE_DRIVER='postgres'
export GOOSE_DBSTRING='postgres://user:password@localhost:5432/db_name?sslmode=disable'
export GOOSE_MIGRATION_DIR='./migrations'
```
* Now source the environment variables
```sh
source env/goose.env
```

### Creating Models and Migrations
* Create a models directory in your projects root directory
```sh
mkdir models
touch models/user.go
```
* Add the following to `models/user.go`
```go
package models

import (
	"gorm.io/gorm"
)

type User struct {
	*gorm.Model
	Name     string
	Email    string
	Password string
}
```
* Now run goose command to create migration file
```sh
mkdir migrations
goose -s create create_user
```
* After this, you'll have import errors. Let's fix this
```sh
go mod tidy
```
* Update the `00001_create_user.go` file to have the following
```go
package migrations

import (
	"app/database"
	"app/models"
	"context"
	"database/sql"

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

func init() {
	goose.AddMigrationContext(upCreateUser, downCreateUser)
}

func upCreateUser(ctx context.Context, tx *sql.Tx) error {
	return database.DB_MIGRATOR.CreateTable(&models.User{})
}

func downCreateUser(ctx context.Context, tx *sql.Tx) error {
	return database.DB_MIGRATOR.DropTable(&models.User{})
}
```
### Bringing It All Together
* Update the `main.go` file to have the following
```go
package main

import (
    "app/database"
    _ "app/migrations" // This loads the migrations init functions

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

func main() {
    err := database.ConnectToDatabase()
    if err != nil {
        panic(err)
    }

    if err := goose.SetDialect("postgres"); err != nil {
        panic(err)
    }
    if err := goose.Up(database.SQL_DB, "migrations"); err != nil {
        panic(err)
    }
    // Start server...
}
```
* Now you can run migrations before starting the server
```sh
go run .
```