Skip to content

sqlx

Databases & CachingDatabaseGo

What it is

sqlx is a thin set of extensions over Go's database/sql that adds struct scanning, named parameters and helpers, without becoming an ORM.

sqlx wraps sql.DB and adds Get, Select, StructScan and named queries. You keep full control of the SQL; sqlx only removes the mapping boilerplate.

Installation

go get github.com/jmoiron/sqlx

Getting started

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

Scanning into structs
type Book struct {
    ID    int    `db:"id"`
    Title string `db:"title"`
    Year  int    `db:"year"`
}

db, err := sqlx.Connect("postgres", dsn)

// One row.
var book Book
err = db.GetContext(ctx, &book, "SELECT id, title, year FROM books WHERE id = $1", 42)

// Many rows.
var books []Book
err = db.SelectContext(ctx, &books, "SELECT id, title, year FROM books WHERE year > $1", 1990)
Get expects exactly one row and returns sql.ErrNoRows otherwise; Select fills a slice and returns no error when nothing matches.
Named parameters
_, err := db.NamedExecContext(ctx,
    `INSERT INTO books (title, year) VALUES (:title, :year)`,
    map[string]any{"title": "Dune", "year": 1965})

// Or straight from a struct, using the db tags.
_, err = db.NamedExecContext(ctx,
    `INSERT INTO books (title, year) VALUES (:title, :year)`, book)
Named parameters keep long INSERT and UPDATE statements readable, and remove the risk of mismatching positional arguments.

Advanced usage

Where the library earns its place over a simpler alternative.

IN clauses with variable length
ids := []int{1, 2, 3, 4}

// database/sql cannot expand a slice — sqlx.In rewrites the query.
query, args, err := sqlx.In("SELECT * FROM books WHERE id IN (?)", ids)
if err != nil { return err }

// Rebind converts ? placeholders to the driver's style ($1, $2 for Postgres).
query = db.Rebind(query)

var books []Book
err = db.SelectContext(ctx, &books, query, args...)
This is the single most useful thing sqlx adds. Building an IN clause by string concatenation is how SQL injection gets introduced.

Errors and fixes

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

missing destination name X in *Book
A selected column has no matching field or db tag. Add the tag, alias the column, or select fewer columns.
sql: no rows in result set
Get found nothing. Check with errors.Is(err, sql.ErrNoRows) and treat it as a not-found case rather than a failure.

Best practices

  • Tag every struct field with db:"column" — sqlx will error on unmatched columns, which catches typos early.
  • Use the Context variants of every method so queries respect cancellation.
  • Set db.SetMaxOpenConns and SetConnMaxLifetime; the defaults are unlimited and will exhaust the database under load.
  • Prefer sqlx.In over building IN lists by hand.

Background

Why it exists, and what it was reacting to.

Written by Jason Moiron for developers who wanted to keep writing SQL but stop writing rows.Scan boilerplate. It stays deliberately close to the standard library, so everything you know about database/sql still applies.