Database
PostgreSQL
Wire pgx and use PostgreSQL-backed GoLazy packages.
Open the pool once
Open PostgreSQL in init/dependencies.go, put the pool in the application
context with pg.WithPool, and let later initializers read it with
pg.FromContext.
package appinit
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
"golazy.dev/lazydeps"
"golazy.dev/pg"
)
func Dependencies(deps *lazydeps.Scope) error {
_, err := lazydeps.Service(deps, "postgres", func(ctx context.Context) (
context.Context,
*pgxpool.Pool,
error,
context.CancelFunc,
) {
db, err := pg.OpenEnv(ctx, "DATABASE_URL")
if err != nil {
return ctx, nil, err, nil
}
return pg.WithPool(ctx, db), db, nil, db.Close
})
return err
}
The dependency graph owns shutdown. Jobs, request handlers, and other services can now read the same app-owned pool from context.
Package map
The core golazy.dev module does not import PostgreSQL drivers. Concrete
implementations live in golazy.dev/pg:
pg: sharedpgxpoolhelpers and context helpers.pgmigrate:lazymigrate.Backendfor PostgreSQL.pgjobs:lazyjobs.Backendfor PostgreSQL.pgfiles:lazyfiles.Repositoryfor PostgreSQL.pgmedia:lazymedia.Repositoryfor PostgreSQL.pgstorage:lazystorageobject backend for PostgreSQL.withpg: embedded PostgreSQL helper for integration tests.
Migrations
Use golazy.dev/pg/pgmigrate as the database backend and include both app and
package migration sources:
var catalog lazymigrate.Catalog
_ = catalog.Add("postgres", lazymigrate.ForDatabase(app.Migrations, "postgres"))
_ = catalog.Add("postgres", pgjobs.Migrations())
_ = catalog.Add("postgres", pgfiles.Migrations())
_ = catalog.Add("postgres", pgmedia.Migrations())
_ = catalog.Add("postgres", pgstorage.Migrations())
migrator, err := lazymigrate.New(lazymigrate.Config{
Backend: pgmigrate.New(db),
Sources: catalog.Sources("postgres"),
})
PostgreSQL migrations use SQL files with lazy markers:
-- +lazy Up
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL
);
-- +lazy Down
DROP TABLE posts;
pgmigrate creates lazy_migrations, lists applied IDs, runs the selected
section in a transaction, and uses a PostgreSQL advisory lock while applying a
step. Read Migrations for the backend contract.
Jobs
Use pgjobs.New(db) when queued work must survive process restarts:
Jobs: func(ctx context.Context) (lazyjobs.Config, error) {
db, ok := pg.FromContext(ctx)
if !ok {
return lazyjobs.Config{}, errors.New("postgres is not configured")
}
return lazyjobs.Config{
Backend: pgjobs.New(db),
Define: jobs.DefinedJobs,
}, nil
},
Include pgjobs.Migrations() in the migration catalog before starting workers.
Read Background Jobs for the job backend interface and
runner behavior.
Object storage
Use pgstorage.New(db) when object bytes should live in PostgreSQL:
objects := pgstorage.New(db)
info, _, err := objects.Put(
ctx,
"uploads/avatar.txt",
strings.NewReader("hello"),
lazystorage.ContentType{Value: "text/plain"},
)
pgstorage implements read, write, delete, and list interfaces. It is useful
for small applications, tests, and database-only deployments. For large public
files or CDN-backed delivery, prefer filesystem or S3-compatible object storage.
File catalogs
Use pgfiles.New(db) as the lazyfiles.Repository while object bytes stay in
a named lazystorage backend:
files := &lazyfiles.Files{
Repository: pgfiles.New(db),
Storages: map[string]lazystorage.Storage{
"postgres": pgstorage.New(db),
},
DefaultStorage: "postgres",
RoutePrefix: "/files",
SigningKey: []byte(os.Getenv("FILE_SIGNING_KEY")),
}
Include pgfiles.Migrations() in the migration catalog. Read
Media for how lazyfiles fits between object storage
and generated media.
Media variants
Use pgmedia.New(db) as the lazymedia.Repository:
media := &lazymedia.Media{
Files: MediaFiles{Files: files},
Repository: pgmedia.New(db),
Processor: imageProcessor,
}
pgmedia stores variant relationships and status. The source and output files
still go through the file service. Include pgmedia.Migrations() in the
migration catalog before using the service.
Asset uploads
lazyassets does not have a separate PostgreSQL repository. Its export path
uses a lazystorage.Writer, so pgstorage can be the target:
registry := lazyassets.New()
_ = registry.AddFS(app.Public)
err := registry.Upload(ctx, pgstorage.New(db), lazyassets.WithUploadPrefix("assets"))
For public production assets, object storage with CDN-friendly URLs is usually the better backend. PostgreSQL is available when a deployment needs one database-backed artifact store.
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 18 -- go test ./pg/...
Go tests can use withpg.Test when a package needs one subtest per PostgreSQL
version.