AI coding assistants are great at generating boilerplate. But for database code in Go, there is a better option: one that generates it from your SQL itself, with zero hallucination risk. It is called sqlc .
What sqlc does
You give it your SQL query files and your schema. It generates type-safe Go functions. Setup is a single config file:
# sqlc.yaml
sql:
- engine: "postgresql"
queries: "db/queries"
schema: "db/models"
gen:
go:
package: "repo"
sql_package: "pgx/v5"
out: "db/repo"
Point it at your queries, point it at your schema, run sqlc generate. Done.
SQL in, typed Go out
Here is the full workflow. You write a SQL file with a one-line annotation:
-- name: GetUserByEmail :one
SELECT * FROM users WHERE email = $1;
sqlc generates the Go function:
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
row := q.db.QueryRow(ctx, getUserByEmail, email)
var i User
err := row.Scan(&i.ID, &i.Name, &i.Email, ...)
return i, err
}
You call it directly in your handler:
user, err := queries.GetUserByEmail(ctx, email)
if errors.Is(err, pgx.ErrNoRows) {
// handle not found
}
One annotation comment. One typed function. No glue code to write.
The AI-era angle
With GORM, you would prompt your LLM something like: “write a Go function to fetch a user by email, handle not found, return the struct.” That is 50-100 tokens of output, probably with subtly wrong method names or a generic Find() that returns all columns when you only need two.
With sqlc: you write the SQL yourself (you already know SQL), run make sqlc, get exact Go. Zero AI tokens spent on scaffolding. Zero drift between what you intended and what runs in production.
The SQL is also the source of truth. Need to optimise a slow query? Change the SQL, re-run sqlc, the Go updates automatically. With an ORM the generated SQL is a black box, and your AI assistant has to reverse-engineer what the ORM will produce before it can help you optimise it.
For queries with multiple parameters, sqlc also generates a typed params struct:
-- name: CreateUser :one
INSERT INTO users (name, email, password_hash, plan_id)
VALUES ($1, $2, $3, $4)
RETURNING *;
user, err := queries.CreateUser(ctx, repo.CreateUserParams{
Name: &name,
Email: email,
PasswordHash: hash,
PlanID: nil,
})
No positional argument mistakes. The compiler catches mismatches before your AI assistant can introduce them.
One gotcha
The db/repo/ directory is generated output. Never edit it directly. Treat it like a compiled binary. Any change you make will be wiped the next time you run sqlc generate. Add make sqlc to your pre-build step so you never forget.
In a world where everyone reaches for an LLM to write database boilerplate, sqlc is the tool that makes that prompt unnecessary.