Use Atlas + GORM for database migrations
Atlas is a popular tool for managing database migrations. GORM is a popular ORM for Go.
Encore provides excellent support for using them together to easily manage database schemas and migrations. Encore executes database migrations using golang-migrate, which Atlas supports out-of-the-box. This means that you can use Atlas to manage your Encore database migrations.
The easiest way to use Atlas + GORM together is with Atlas's support for external schemas.
Setting up GORM
To set up your Encore application with GORM, start by installing the GORM package and associated Postgres driver:
go get -u gorm.io/gorm gorm.io/driver/postgres
Then, in the service that you want to use GORM for, add the *gorm.DB
as a dependency
in your service struct (create a service struct if you don't already have one).
For example, if you had a service called blog
:
blog/blog.gopackage blog
import (
"encore.dev/storage/sqldb"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
//encore:service
type Service struct {
db *gorm.DB
}
var blogDB = sqldb.NewDatabase("blog", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
// initService initializes the site service.
// It is automatically called by Encore on service startup.
func initService() (*Service, error) {
db, err := gorm.Open(postgres.New(postgres.Config{
Conn: blogDB.Stdlib(),
}))
if err != nil {
return nil, err
}
return &Service{db: db}, nil
}
Finally, create the migrations
directory inside the blog
directory if it doesn't already exist.
This is where Atlas will put your database migrations.
Setting up Atlas
First install Atlas.
Then, add an atlas.hcl
file inside the blog
directory:
blog/atlas.hcldata "external_schema" "gorm" {
program = ["env", "ENCORERUNTIME_NOPANIC=1", "go", "run", "./scripts/atlas-gorm-loader.go"]
}
env "local" {
src = data.external_schema.gorm.url
migration {
dir = "file://migrations"
format = golang-migrate
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
Next, we need to create the atlas-gorm-loader
script referenced above.
It will use the atlas-provider-gorm library provided by Atlas.
Create the file as follows:
blog/scripts/atlas-gorm-loader.gopackage main
import (
"fmt"
"io"
"os"
_ "ariga.io/atlas-go-sdk/recordriver"
"ariga.io/atlas-provider-gorm/gormschema"
"encore.app/blog"
)
// Define the models to generate migrations for.
var models = []any{
&blog.Post{},
&blog.Comment{},
}
func main() {
stmts, err := gormschema.New("postgres").Load(models...)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load gorm schema: %v\n", err)
os.Exit(1)
}
io.WriteString(os.Stdout, stmts)
}
Creating migrations
To wrap things up, let's create a script to automate the process of generating migrations:
blog/scripts/generate-migration#!/bin/bash
set -eu
DB_NAME=blog
MIGRATION_NAME=${1:-}
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Reset the shadow database
encore db reset --shadow $DB_NAME
# GORM executes Go code without initializing Encore when generating migrations,
# so configure the Encore runtime to be aware that this is expected.
export ENCORERUNTIME_NOPANIC=1
# Generate the migration
atlas migrate diff $MIGRATION_NAME --env local --dev-url "$(encore db conn-uri --shadow $DB_NAME)&search_path=public"
Finally let's make the script executable, and generate our first migration:
$ chmod +x blog/scripts/generate-migration$ cd blog && ./scripts/generate-migration init
This will generate a new migration file in the blog/migrations
directory, which
will be automatically applied when running encore run
.