Skip to main content

SQLC Queries

Write type-safe SQL queries with SQLC.

Directory Structure

internal/db/
├── queries/ # Your SQL query files
├── generated/ # SQLC output (do not edit)
└── sqlc.yaml # Configuration

Query Annotations

AnnotationReturnsUse Case
:oneSingle row or errorGet by ID
:manySlice of rowsList, search
:execOnly errorINSERT, UPDATE, DELETE
:execrowsRow count + errorBatch operations

Writing Queries

Create internal/db/queries/users.sql:

-- name: GetUser :one
SELECT id, email, name FROM users WHERE id = $1;

-- name: ListUsers :many
SELECT id, email, name FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2;

-- name: CreateUser :one
INSERT INTO users (id, email, name) VALUES ($1, $2, $3) RETURNING *;

-- name: UpdateUser :exec
UPDATE users SET name = $2 WHERE id = $1;

-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;

Generate code:

make generate

Using Generated Code

type UserRepository struct {
queries *generated.Queries
}

func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{queries: generated.New(db)}
}

func (r *UserRepository) GetByID(ctx context.Context, id string) (*User, error) {
row, err := r.queries.GetUser(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
}
return nil, fmt.Errorf("get user: %w", err)
}
return &User{ID: row.ID, Email: row.Email, Name: row.Name}, nil
}

Transactions

func (r *UserRepository) CreateWithProfile(ctx context.Context, user *User) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()

queries := r.queries.WithTx(tx)
// ... use queries ...
return tx.Commit()
}

Query Patterns

Nullable fields use sql.NullString, sql.NullInt64, etc.

Dynamic filters:

-- name: SearchUsers :many
SELECT * FROM users
WHERE ($1::text IS NULL OR email LIKE '%' || $1 || '%')
ORDER BY created_at DESC;

Configuration

The generated sqlc.yaml:

version: "2"
sql:
- engine: "postgresql"
queries: "queries/"
schema: "migrations/"
gen:
go:
package: "generated"
out: "generated"
emit_json_tags: true
emit_empty_slices: true

Type Overrides

gen:
go:
overrides:
- db_type: "uuid"
go_type: "github.com/google/uuid.UUID"

Best Practices

  • Use descriptive names: GetUserByEmail, ListActiveUsers
  • Organize queries by domain: users.sql, posts.sql
  • Validate before generating - SQLC checks SQL against your schema