Documentation
¶
Overview ¶
Package db provides PostgreSQL database utilities optimized for SaaS applications.
This package wraps github.com/jackc/pgx/v5/pgxpool to provide connection pooling, health checks, and database migrations with sensible defaults for production workloads.
Features ¶
- Connection pooling with configurable limits and timeouts
- Automatic retry logic with exponential backoff during startup
- Health check function compatible with standard health check interfaces
- Database migrations using github.com/pressly/goose/v3
- Environment-based configuration for deployment convenience
Configuration ¶
All settings are loaded from environment variables:
DATABASE_CONN_URL - PostgreSQL connection URL (required) DATABASE_MAX_OPEN_CONNS - Maximum open connections (default: 10) DATABASE_MIN_CONNS - Minimum idle connections (default: 5) DATABASE_HEALTHCHECK_PERIOD - Health check interval (default: 1m) DATABASE_MAX_CONN_IDLE_TIME - Maximum connection idle time (default: 10m) DATABASE_MAX_CONN_LIFETIME - Maximum connection lifetime (default: 30m) DATABASE_RETRY_ATTEMPTS - Connection retry attempts (default: 3) DATABASE_RETRY_INTERVAL - Base retry interval (default: 5s) DATABASE_MIGRATIONS_TABLE - Migrations table name (default: schema_migrations)
Usage ¶
Basic connection setup with Config:
import (
"context"
"log"
"os"
"github.com/dmitrymomot/forge/pkg/db"
)
func main() {
ctx := context.Background()
pool, err := db.Open(ctx, db.Config{
URL: os.Getenv("DATABASE_CONN_URL"),
MaxConns: 10,
MinConns: 5,
})
if err != nil {
log.Fatal(err)
}
defer pool.Close()
}
Health Checks ¶
The Healthcheck function returns a closure suitable for health check endpoints:
import (
"context"
"net/http"
"github.com/dmitrymomot/forge/pkg/db"
)
func healthHandler(pool *db.Pool) http.HandlerFunc {
healthFn := db.Healthcheck(pool)
return func(w http.ResponseWriter, r *http.Request) {
if err := healthFn(r.Context()); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}
}
Transactions ¶
The WithTx helper provides automatic transaction management with rollback on error:
import (
"context"
"github.com/dmitrymomot/forge/pkg/db"
)
err := db.WithTx(ctx, pool, func(tx pgx.Tx) error {
// Execute queries using tx
return tx.QueryRow(ctx, "SELECT 1").Scan(&result)
})
if err != nil {
// Transaction was rolled back automatically
}
Migrations ¶
Run database migrations using embedded SQL files:
import (
"context"
"embed"
"log/slog"
"github.com/dmitrymomot/forge/pkg/db"
)
//go:embed *.sql
var migrations embed.FS
err := db.Migrate(ctx, pool, migrations, logger)
if err != nil {
log.Fatal(err)
}
Error Handling ¶
The package defines sentinel errors for common failure modes:
- ErrFailedToParseDBConfig - Invalid connection string format
- ErrFailedToOpenDBConnection - Connection failed after all retries
- ErrHealthcheckFailed - Database ping failed
- ErrSetDialect - Migration dialect configuration error
- ErrApplyMigrations - Migration execution failed
Errors are wrapped using errors.Join to preserve the original error context.
Index ¶
- Variables
- func Healthcheck(conn *pgxpool.Pool) func(context.Context) error
- func Migrate(ctx context.Context, pool *pgxpool.Pool, migrations embed.FS, log *slog.Logger) error
- func MustOpen(ctx context.Context, cfg Config, opts ...Option) *pgxpool.Pool
- func Open(ctx context.Context, cfg Config, opts ...Option) (*pgxpool.Pool, error)
- func Shutdown(pool *pgxpool.Pool) func(ctx context.Context) error
- func WithTx(ctx context.Context, pool *pgxpool.Pool, fn func(tx pgx.Tx) error) error
- type Config
- type Option
Constants ¶
This section is empty.
Variables ¶
var ( ErrFailedToParseDBConfig = errors.New("db: failed to parse database configuration") ErrFailedToOpenDBConnection = errors.New("db: failed to open database connection") ErrHealthcheckFailed = errors.New("db: healthcheck failed") ErrSetDialect = errors.New("db migrator: failed to set dialect") ErrApplyMigrations = errors.New("db migrator: failed to apply migrations") )
Functions ¶
func Healthcheck ¶
Healthcheck returns a closure that validates database connectivity for health endpoints. Uses closure pattern to inject the connection dependency while maintaining compatibility with standard health check interfaces that expect func(context.Context) error.
func Migrate ¶
Migrate runs database migrations using the embedded SQL files. Uses hardcoded defaults: FS root (".") directory and "schema_migrations" table. Pass nil for log to disable migration logging.
func MustOpen ¶
MustOpen creates a connection pool or exits on failure. Use for simple applications where startup failure is fatal.
Example:
pool := db.MustOpen(ctx, db.Config{URL: os.Getenv("DATABASE_URL")},
db.WithMigrations(migrations),
db.WithLogger(log),
)
func Open ¶
Open creates a PostgreSQL connection pool with sensible defaults. Supports optional migrations and configurable pool settings via Config.
Example:
//go:embed *.sql
var migrations embed.FS
pool, err := db.Open(ctx, db.Config{URL: "postgres://user:pass@host:5432/db"},
db.WithMigrations(migrations),
db.WithLogger(log),
)
func Shutdown ¶
Shutdown returns a function that gracefully closes the database connection pool. Use with forge.WithShutdownHook().
Example:
app := forge.New(
forge.WithShutdownHook(db.Shutdown(pool)),
)
Types ¶
type Config ¶
type Config struct {
URL string `env:"URL,required"`
MaxConns int32 `env:"MAX_CONNS" envDefault:"10"`
MinConns int32 `env:"MIN_CONNS" envDefault:"5"`
HealthCheckPeriod time.Duration `env:"HEALTH_CHECK_PERIOD" envDefault:"1m"`
MaxConnIdleTime time.Duration `env:"MAX_CONN_IDLE_TIME" envDefault:"10m"`
MaxConnLifetime time.Duration `env:"MAX_CONN_LIFETIME" envDefault:"30m"`
RetryAttempts int `env:"RETRY_ATTEMPTS" envDefault:"3"`
RetryInterval time.Duration `env:"RETRY_INTERVAL" envDefault:"5s"`
}
Config holds database connection configuration.
type Option ¶
type Option func(*options)
Option configures database connection.
func WithLogger ¶
WithLogger sets the logger for migrations and connection events.
func WithMigrations ¶
WithMigrations enables automatic migrations using embedded SQL files.