App
PostgreSQL
Wire a GoLazy application to PostgreSQL-backed migrations, jobs, and storage packages.
Start with init/db.go
PostgreSQL is application glue first. Create the pool once, pass it to
lazyapp.Config, and put the reusable database helper in init/db.go.
// init/app.go
func App() *lazyapp.App {
db, err := OpenDB(context.Background())
if err != nil {
panic(err)
}
return lazyapp.New(lazyapp.Config{
Name: "sample_app",
Drawer: Draw,
Public: app.Public,
Views: app.Views,
Dependencies: func(deps *lazydeps.Scope) error {
if err := Database(deps, db); err != nil {
return err
}
return Dependencies(deps)
},
Jobs: lazyjobs.Config{
Backend: pgjobs.New(db),
Define: jobs.DefinedJobs,
},
})
}
// init/db.go
package appinit
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
"golazy.dev/lazydeps"
"golazy.dev/pg"
)
type dbKey struct{}
func OpenDB(ctx context.Context) (*pgxpool.Pool, error) {
return pg.OpenEnv(ctx, "DATABASE_URL")
}
func Database(deps *lazydeps.Scope, db *pgxpool.Pool) error {
_, err := lazydeps.Service(deps, "postgres", func(ctx context.Context) (
context.Context,
*pgxpool.Pool,
error,
context.CancelFunc,
) {
return context.WithValue(ctx, dbKey{}, db), db, nil, db.Close
})
return err
}
func DB(ctx context.Context) (*pgxpool.Pool, bool) {
db, ok := ctx.Value(dbKey{}).(*pgxpool.Pool)
return db, ok
}
The pool is app-owned. PostgreSQL service packages receive the pool, but the dependency graph closes it.
Package map
The core golazy.dev module keeps PostgreSQL out of its dependency graph.
Concrete implementations live in golazy.dev/pg:
golazy.dev/pg: sharedpgxpoolhelpers.golazy.dev/pg/pgmigrate: PostgreSQL backend forgolazy.dev/lazymigrate.golazy.dev/pg/pgjobs: PostgreSQL backend forgolazy.dev/lazyjobs.golazy.dev/pg/withpg: embedded PostgreSQL helper for local integration tests.
Continue with Dependencies And Services for dependency ownership and Background Jobs for job registration.
Migrations
Application migrations live under migrations/<database>/, with a matching
migrations/migrations.toml entry:
[postgres]
backend = "golazy.dev/pg/pgmigrate"
url_env = "DATABASE_URL"
The PostgreSQL backend owns the SQL format. Use lazy markers, not Goose markers:
-- +lazy Up
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL
);
-- +lazy Down
DROP TABLE posts;
lazymigrate loads sortable migration files, asks the backend which migrations
have already run, computes the diff, and calls the backend for each up or down
step. pgmigrate creates the lazy_migrations metadata table, lists applied
migration ids, runs the selected section in a transaction, and records the
checksum.
Package backends can embed their own migrations. For example, pgjobs exposes
the lazy job table migration alongside its lazyjobs.Backend.
Local tests
The golazy.dev/pg integration tests read GOLAZY_PG_DATABASE_URL.
golazy.dev/pg/withpg starts embedded PostgreSQL and sets both
DATABASE_URL and GOLAZY_PG_DATABASE_URL for the command it runs.
go run ./pg/withpg/cmd/withpg --version 16 --version 17 -- go test ./pg/...
Go tests can use withpg.Test to create one subtest per configured PostgreSQL
version:
withpg.Test(t, withpg.Config{PgVersions: []string{"16", "17"}}, func(t *testing.T, db withpg.DB) {
t.Parallel()
// Use db.URL() or db.Client(t.Context()).
})
Generator direction
lazy generate pg will later modify an existing app. It should create or merge
init/db.go, add the golazy.dev/pg module dependency, create
migrations/migrations.toml, and wire selected PostgreSQL package backends
through lazyapp.Config.