pgx is a pure Go driver and toolkit for PostgreSQL providing a native high-performance interface with PostgreSQL-specific features plus a database/sql compatibility adapter.
—
Frequently used patterns for everyday pgx usage. These patterns cover 80% of typical use cases.
var name string
var email string
err := pool.QueryRow(ctx, "SELECT name, email FROM users WHERE id = $1", userID).Scan(&name, &email)
if errors.Is(err, pgx.ErrNoRows) {
// user not found
}type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
rows, err := pool.Query(ctx, "SELECT id, name, email FROM users WHERE active = $1", true)
if err != nil {
return err
}
users, err := pgx.CollectRows(rows, pgx.RowToStructByName[User])
if err != nil {
return err
}
// users is []Usertag, err := pool.Exec(ctx, "DELETE FROM users WHERE id = $1", userID)
if err != nil {
return err
}
fmt.Printf("Deleted %d rows\n", tag.RowsAffected())err := pgx.BeginFunc(ctx, pool, func(tx pgx.Tx) error {
_, err := tx.Exec(ctx, "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, fromAccount)
if err != nil {
return err // automatic rollback
}
_, err = tx.Exec(ctx, "UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, toAccount)
if err != nil {
return err // automatic rollback
}
return nil // automatic commit
})rows := [][]any{
{"Alice", "alice@example.com"},
{"Bob", "bob@example.com"},
{"Charlie", "charlie@example.com"},
}
count, err := pool.CopyFrom(
ctx,
pgx.Identifier{"users"},
[]string{"name", "email"},
pgx.CopyFromRows(rows),
)
fmt.Printf("Copied %d rows\n", count)batch := &pgx.Batch{}
var user1Name, user2Name string
batch.Queue("SELECT name FROM users WHERE id = $1", 1).QueryRow(func(row pgx.Row) error {
return row.Scan(&user1Name)
})
batch.Queue("SELECT name FROM users WHERE id = $1", 2).QueryRow(func(row pgx.Row) error {
return row.Scan(&user2Name)
})
batch.Queue("UPDATE stats SET last_access = now()")
br := pool.SendBatch(ctx, batch)
err := br.Close()
if err != nil {
return err
}
// user1Name and user2Name are now populatedrows, err := pool.Query(ctx,
"SELECT * FROM orders WHERE user_id = @user_id AND status = @status",
pgx.NamedArgs{
"user_id": 42,
"status": "pending",
})rows, err := pool.Query(ctx, "SELECT id, name, email FROM users LIMIT 10")
if err != nil {
return err
}
userMaps, err := pgx.CollectRows(rows, pgx.RowToMap)
// userMaps is []map[string]anyFor very large result sets where loading all rows into memory isn't practical:
rows, err := pool.Query(ctx, "SELECT id, data FROM large_table")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id int64
var data string
if err := rows.Scan(&id, &data); err != nil {
return err
}
// process one row at a time
processRow(id, data)
}
return rows.Err()type User struct {
ID int64
Name string
Email pgtype.Text // can be NULL
}
rows, err := pool.Query(ctx, "SELECT id, name, email FROM users")
users, err := pgx.CollectRows(rows, pgx.RowToStructByPos[User])
for _, u := range users {
if u.Email.Valid {
fmt.Println("Email:", u.Email.String)
} else {
fmt.Println("Email: NULL")
}
}type User struct {
ID int64
Name string
Email *string // nil if NULL
}
rows, err := pool.Query(ctx, "SELECT id, name, email FROM users")
users, err := pgx.CollectRows(rows, pgx.RowToStructByPos[User])
for _, u := range users {
if u.Email != nil {
fmt.Println("Email:", *u.Email)
} else {
fmt.Println("Email: NULL")
}
}import "github.com/jackc/pgx/v5/pgtype/zeronull"
type User struct {
ID int64
Name string
Email zeronull.Text // "" is stored/scanned as NULL
}
rows, err := pool.Query(ctx, "SELECT id, name, email FROM users")
users, err := pgx.CollectRows(rows, pgx.RowToStructByPos[User])import "github.com/jackc/pgx/v5/pgtype"
// Query array
var tags pgtype.FlatArray[string]
err := pool.QueryRow(ctx, "SELECT tags FROM articles WHERE id = $1", articleID).Scan(&tags)
// Insert array
newTags := pgtype.FlatArray[string]{"golang", "postgresql", "database"}
_, err = pool.Exec(ctx, "INSERT INTO articles (title, tags) VALUES ($1, $2)", title, newTags)type Metadata struct {
Author string `json:"author"`
Tags []string `json:"tags"`
Version int `json:"version"`
}
// Query JSONB
var meta Metadata
err := pool.QueryRow(ctx, "SELECT metadata FROM documents WHERE id = $1", docID).Scan(&meta)
// Insert JSONB
newMeta := Metadata{
Author: "Alice",
Tags: []string{"draft", "review"},
Version: 1,
}
_, err = pool.Exec(ctx, "INSERT INTO documents (title, metadata) VALUES ($1, $2)", title, newMeta)conn, err := pool.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
// Use conn for multiple operations
_, err = conn.Exec(ctx, "SET work_mem = '256MB'")
rows, err := conn.Query(ctx, "SELECT * FROM large_table ORDER BY created_at")
// ... process rowsOr use AcquireFunc for auto-release:
err := pool.AcquireFunc(ctx, func(conn *pgxpool.Conn) error {
_, err := conn.Exec(ctx, "SET work_mem = '256MB'")
if err != nil {
return err
}
rows, err := conn.Query(ctx, "SELECT * FROM large_table ORDER BY created_at")
// ... process rows
return nil
})// Load and register custom enum type
t, err := conn.LoadType(ctx, "my_status_enum")
if err != nil {
return err
}
conn.TypeMap().RegisterType(t)
// Load array type
arrType, err := conn.LoadType(ctx, "_my_status_enum")
conn.TypeMap().RegisterType(arrType)
// Now you can use the type
var status string
err = conn.QueryRow(ctx, "SELECT status FROM orders WHERE id = $1", orderID).Scan(&status)Normally pgx auto-prepares. For explicit control:
sd, err := conn.Prepare(ctx, "get_user", "SELECT id, name FROM users WHERE email = $1")
if err != nil {
return err
}
// Use prepared statement (by name)
var id int64
var name string
err = conn.QueryRow(ctx, "get_user", "alice@example.com").Scan(&id, &name)
// Deallocate when done
conn.Deallocate(ctx, "get_user")_, err := conn.Exec(ctx, "LISTEN my_channel")
if err != nil {
return err
}
for {
notification, err := conn.WaitForNotification(ctx)
if err != nil {
return err
}
fmt.Printf("Received: %s on channel %s\n", notification.Payload, notification.Channel)
}import "github.com/jackc/pgx/v5/tracelog"
type myLogger struct{}
func (l *myLogger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) {
log.Printf("[%s] %s %v", level, msg, data)
}
config, _ := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
config.ConnConfig.Tracer = &tracelog.TraceLog{
Logger: &myLogger{},
LogLevel: tracelog.LogLevelInfo,
}
pool, err := pgxpool.NewWithConfig(ctx, config)import "crypto/tls"
config, _ := pgx.ParseConfig(connString)
config.TLSConfig = &tls.Config{
InsecureSkipVerify: false,
ServerName: "myserver.example.com",
}
conn, err := pgx.ConnectConfig(ctx, config)import "github.com/jackc/pgx/v5/pgxtest"
func TestMyQuery(t *testing.T) {
ctr := pgxtest.DefaultConnTestRunner()
pgxtest.RunWithQueryExecModes(context.Background(), t, ctr, nil,
func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
// Test runs once per query execution mode
var count int
err := conn.QueryRow(ctx, "SELECT COUNT(*) FROM users").Scan(&count)
assert.NoError(t, err)
assert.Greater(t, count, 0)
})
}import (
"database/sql"
"github.com/jackc/pgx/v5/stdlib"
_ "github.com/jackc/pgx/v5/stdlib"
)
db, err := sql.Open("pgx", connString)
// Get underlying pgx connection for pgx-specific features
sqlConn, err := db.Conn(ctx)
defer sqlConn.Close()
err = sqlConn.Raw(func(driverConn any) error {
pgxConn := driverConn.(*stdlib.Conn).Conn() // *pgx.Conn
// Use pgx-specific features
_, err := pgxConn.CopyFrom(ctx,
pgx.Identifier{"users"},
[]string{"name", "email"},
pgx.CopyFromRows(rows))
return err
})Install with Tessl CLI
npx tessl i tessl/golang-github-com-jackc-pgx-v5