Getting Started
Andurel v2 uses PostgreSQL through github.com/mbvlabs/andurel/pkg/storage. One shared pgx/v5 pool serves narsilc-backed models and River via storage.Connection.
Connection lifecycle
The web and queue composition roots construct *storage.Postgres and publish it as storage.Connection. Construction validates configuration, builds a pgx pool, applies pool limits, and pings PostgreSQL. A failed ping prevents startup; an Fx lifecycle hook closes the pool during shutdown.
1db, err := storage.NewPostgres(ctx, cfg)
2if err != nil {
3 return nil, err
4}
5lifecycle.Append(fx.Hook{
6 OnStop: func(context.Context) error { return db.Close() },
7})
Connection implements pgx DBTX (Exec, Query, QueryRow) plus Health(ctx) and BeginTransaction(ctx). Pass it directly to narsilc: queries.New(db). Do not create separate pools for models, migrations tooling, and River.
PostgreSQL configuration
Start from storage.DefaultConfig() and replace application identity and credentials. Application config.NewDatabase maps environment variables into this type. Config.Validate requires a PostgreSQL kind, host, port, name, user, and supported SSL mode.
1cfg := storage.DefaultConfig()
2cfg.Name = "orbit_production"
3cfg.User = "orbit"
4cfg.Password = secret
5cfg.SSLMode = "verify-full"
6cfg.ApplicationName = "orbit-web"
7db, err := storage.NewPostgres(ctx, cfg)
Programmatic options override config fields. WithMaxOpenConnections maps to pgxpool MaxConns. Prefer max open connections and ConnectionMaxIdleTime over idle-connection knobs that are not applied to the pgx pool. See Configuration for the full DB_* table.
Models and queries
Application-owned models depend on storage.Connection, wrap a narsilc client, and expose WithTx for transactional work:
1type Products struct {
2 queries *queries.Queries
3}
4
5func NewProducts(db storage.Connection) Products {
6 return Products{queries: queries.New(db)}
7}
8
9func (p Products) WithTx(tx storage.Transaction) Products {
10 return Products{queries: queries.New(tx)}
11}
Transactions
RunInTransaction commits only when its callback succeeds and otherwise rolls back. The Transaction value also implements pgx DBTX, so narsilc takes it directly:
1err := storage.RunInTransaction(ctx, connection,
2 func(ctx context.Context, tx storage.Transaction) error {
3 products := models.NewProducts(connection).WithTx(tx)
4 product, err := products.Create(ctx, data)
5 if err != nil {
6 return err
7 }
8 _, err = queue.InsertTx(ctx, tx, jobs.ProductCreatedArgs{ID: product.ID}, nil)
9 return err
10 },
11)
Calling a model method that uses the original connection from inside the callback escapes the transaction. Pass tx (or a model constructed with WithTx) into participating operations instead.
Migrations and test databases
RunMigrations applies Goose migrations from an fs.FS. Generated projects embed SQL under root migrations/. See Migrations & Seeding.
NewTestCluster starts a PostgreSQL 17 Alpine container for isolated test databases. Prefer one cluster per test package. See Testing.
River insertion and processing
storage.NewQueueInsert builds an insert-only River client on the shared pgx pool (riverpgxv5). Publish it as storage.InsertQueue in the web process. storage.NewQueueProcessor owns processing and belongs in cmd/queue.
Use InsertTx / InsertManyTx when the job must share a PostgreSQL transaction with domain writes. See Queues.