go 29 lines · 1 tab

db/sql prepared statements with context and explicit Close

Leah Thompson Jan 2026
1 tab
package store

import (
  "context"
  "database/sql"
)

type Queries struct {
  db         *sql.DB
  getByEmail *sql.Stmt
}

func New(db *sql.DB) (*Queries, error) {
  stmt, err := db.Prepare(`SELECT id FROM users WHERE email = $1`)
  if err != nil {
    return nil, err
  }
  return &Queries{db: db, getByEmail: stmt}, nil
}

func (q *Queries) Close() error {
  return q.getByEmail.Close()
}

func (q *Queries) UserIDByEmail(ctx context.Context, email string) (string, error) {
  var id string
  err := q.getByEmail.QueryRowContext(ctx, email).Scan(&id)
  return id, err
}
1 file · go Explain with highlit

Prepared statements are useful when the same query runs in hot loops, but they come with lifecycle responsibilities. I prepare once (usually at startup), store the *sql.Stmt, and always close it on shutdown. The important detail is still using QueryRowContext so deadlines and cancellations work; prepared statements don’t magically inherit context. This pattern is also safer than building SQL strings in loops and it can reduce parse overhead on the database side. In production, I pair this with context.WithTimeout at the handler layer and with metrics for query latency so slow queries are obvious. The example below shows a small wrapper that provides a typed method around a prepared statement, keeping call sites clean and consistent.


Related snips

Share this code

Here's the card — post it anywhere.

db/sql prepared statements with context and explicit Close — share card
Link copied