Go + Postgres
I’m assuming you have a Postgres instance running on
:5432
(here is how to spin one with Docker).
To connect to it via go, we will use pgx:
package main
import (
"os"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
var (
postgresURL = fmt.Sprintf(
"user=%s password=%s db=%s",
get("POSTGRES_USER"),
get("POSTGRES_PASSWORD"),
get("POSTGRES_DB"))
)
dbpool, err := pgxpool.New(context.Background(), postgresURL)
if err != nil {
panic(err)
}
defer dbpool.Close()
}
func get(key string) string {
if val := os.Getenv(key); val != "" {
return val
}
panic(key + " required")
}
Migrations
You will often want to automatically apply migrations as your code runs. There exist fully fledged solutions such as Go Migrate, but oftentimes you just wanna keep it simple:
internal/common/db/migrate.go
package db
import (
"github.com/jackc/pgx/v5/pgxpool"
)
func MigrateDir(db *pgxpool.Pool, dir embed.FS) error {
files, err := fs.Glob(dir, "migrations/*.sql")
if err != nil {
return err
}
sort.Strings(files)
ctx := context.Background()
// table keeps track of migrations
if _, err := db.Exec(ctx, `CREATE TABLE IF NOT EXISTS migrations (name TEXT PRIMARY KEY);`); err != nil {
return fmt.Errorf("failed creating migrations table: %w", err)
}
// apply all migrations in order
for _, file := range files {
if err := migrateFile(ctxh db, dir, file); err != nil {
return fmt.Errorf("migration %s: %s", file, err)
}
}
return nil
}
func migrateFile(ctx context.Context, db *pgxpool.Pool, dir embed.FS, file string) error {
tx, err := db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
// n is the number of times the migration has been executed
var n int
if err := tx.QueryRow(ctx, `SELECT COUNT(*) FROM migrations WHERE name = $1`, file).Scan(&n); err != nil {
return err
}
if n > 0 {
return nil
}
// read and execute file
bs, err := fs.ReadFile(dir, file)
if err != nil {
return err
}
if _, err := tx.Exec(ctx, string(bs)); err != nil {
return err
}
// no errors, so we log it as success and commit all changes
if _, err := tx.Exec(ctx, `INSERT INTO migrations (name) VALUES ($1)`, file); err != nil {
return err
}
return tx.Commit(ctx)
}
package main
import (
...
"{{mod}}/internal/common/db"
)
func main() {
...
if err := db.MigrateDir(dbpool, migrateDir); err != nil {
panic(err)
}
}