Skip to content

pgx

Databases & CachingDatabaseGo

What it is

pgx is a PostgreSQL driver and toolkit for Go that bypasses database/sql to expose PostgreSQL-specific features and significantly better performance.

The native pgx API supports connection pooling, COPY, LISTEN/NOTIFY, native PostgreSQL types such as arrays and JSONB, and binary protocol transfers. Use pgxpool for concurrent applications.

Installation

go get github.com/jackc/pgx/v5

Getting started

The smallest useful thing you can do with it, and what each part means.

Pool, query, scan
pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if err != nil { return err }
defer pool.Close()

rows, err := pool.Query(ctx, "SELECT id, title FROM books WHERE year > $1", 1990)
if err != nil { return err }

// CollectRows removes the manual rows.Next loop.
books, err := pgx.CollectRows(rows, pgx.RowToStructByName[Book])
pgx.CollectRows with RowToStructByName is the generics-based scanning added in v5, and removes most of the boilerplate that made database/sql tedious.
Bulk insert with COPY
rowsAffected, err := pool.CopyFrom(ctx,
    pgx.Identifier{"books"},
    []string{"title", "year"},
    pgx.CopyFromSlice(len(books), func(i int) ([]any, error) {
        return []any{books[i].Title, books[i].Year}, nil
    }),
)
COPY is dramatically faster than looping INSERTs for bulk loads — often by an order of magnitude — and is only reachable because pgx bypasses database/sql.

Advanced usage

Where the library earns its place over a simpler alternative.

Transactions and JSONB
tx, err := pool.Begin(ctx)
if err != nil { return err }
defer tx.Rollback(ctx) // no-op once Commit succeeds

metadata := map[string]any{"tags": []string{"scifi"}, "rating": 4.5}
_, err = tx.Exec(ctx,
    "INSERT INTO books (title, metadata) VALUES ($1, $2)",
    "Dune", metadata) // encoded to JSONB natively
if err != nil { return err }

return tx.Commit(ctx)
The deferred Rollback is the idiomatic safety net: it runs on any early return and does nothing if the transaction already committed.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

no rows in result set
QueryRow returns pgx.ErrNoRows. Check with errors.Is(err, pgx.ErrNoRows) — note this is pgx's own error, not sql.ErrNoRows.
Constraint violations are hard to distinguish
Cast to *pgconn.PgError and inspect .Code — 23505 is unique_violation, 23503 is foreign_key_violation.

Best practices

  • Use pgxpool rather than a single connection for anything concurrent.
  • Prefer the native pgx API over the database/sql adapter unless you need driver portability.
  • Use CopyFrom for bulk inserts instead of looping Exec.
  • Always pass a context; pgx uses it for both cancellation and statement timeouts.

Background

Why it exists, and what it was reacting to.

Built by Jack Christensen, pgx exists because database/sql's lowest-common-denominator interface hides much of what PostgreSQL can do. It offers both a native API and a database/sql-compatible adapter.