DEV Community

Huseyn
Huseyn

Posted on

Complete Guide: Using `sqlc` with Your Go Project

πŸš€ What is sqlc?

sqlc is a code generation tool that converts your SQL queries into type-safe Go functions. It eliminates manual boilerplate code and avoids ORMs.


βœ… Benefits

  • Type-safe queries
  • No ORM overhead
  • Native SQL with full control
  • Auto-generated Go models & methods

πŸ“¦ Installation

CLI

brew install sqlc # macOS # or go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest 
Enter fullscreen mode Exit fullscreen mode

πŸ“ Recommended Project Structure

myapp/ β”œβ”€β”€ db/ β”‚ β”œβ”€β”€ migrations/ β”‚ β”œβ”€β”€ queries/ β”‚ β”‚ β”œβ”€β”€ users.sql β”‚ └── schema.sql β”œβ”€β”€ sqlc.yaml β”œβ”€β”€ go.mod └── main.go 
Enter fullscreen mode Exit fullscreen mode

βš™οΈ sqlc.yaml Configuration

version: "2" sql: - engine: "postgresql" queries: "db/queries/" schema: "db/migrations/" gen: go: package: "db" out: "db/sqlc" sql_package: "pgx/v5" emit_json_tags: true emit_interface: true 
Enter fullscreen mode Exit fullscreen mode

✍️ Writing SQL Queries

db/queries/users.sql

-- name: CreateUser :one INSERT INTO users (username, email) VALUES ($1, $2) RETURNING id, username, email; -- name: GetUser :one SELECT id, username, email FROM users WHERE id = $1; -- name: ListUsers :many SELECT id, username, email FROM users ORDER BY id DESC; 
Enter fullscreen mode Exit fullscreen mode

πŸ”– Query Types in sqlc

Tag Purpose Go Return Type
:one Returns a single row. Fails if no row or more than one is returned. Use it for SELECT with unique constraint or primary key filters. (Model, error)
:many Returns multiple rows as a slice. Use for general SELECT queries that return 0 to many rows. ([]Model, error)
:exec Executes query with no result rows. Use for INSERT, UPDATE, or DELETE that don't return rows. (error)
:execrows Executes and also returns the number of rows affected. Useful when you care how many rows were impacted by UPDATE or DELETE. (int64, error)
:copyfrom Generates a method using PostgreSQL's COPY FROM. Use for fast bulk insert operations. (int64, error)

πŸ“„ Example Migration (db/migrations/init.up.sql)

CREATE TABLE users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL, email TEXT UNIQUE NOT NULL ); 
Enter fullscreen mode Exit fullscreen mode

πŸ› οΈ Generating Go Code

sqlc generate 
Enter fullscreen mode Exit fullscreen mode

Creates Go code in db/sqlc with models and query methods.


πŸ§‘β€πŸ’» Using in Go Code

import ( "context" "database/sql" "fmt" _ "github.com/lib/pq" "myapp/db/sqlc" ) dbConn, _ := sql.Open("postgres", "postgresql://root:root@localhost:5432/simple_bank?sslmode=disable") q := sqlc.New(dbConn) user, _ := q.CreateUser(context.Background(), "esha", "esha@email.com") fmt.Println(user) 
Enter fullscreen mode Exit fullscreen mode

🧹 Handling Errors

If you see:

error: Dirty database version -1. Fix and force version. 
Enter fullscreen mode Exit fullscreen mode

Use:

migrate ... force 0 
Enter fullscreen mode Exit fullscreen mode

Then:

migrate ... up 
Enter fullscreen mode Exit fullscreen mode

βœ… Tips

  • Use pgx/v5 for performance
  • Never edit applied migrations
  • Use .PHONY Makefile targets to run sqlc generate, migrate, etc.

More
These annotations like :one, :many, :exec, etc., are special sqlc query tags that tell sqlc:

  • What kind of Go function to generate
  • What kind of return value to expect

They appear in SQL comments just before each query:

-- name: MyFunctionName :<type> <SQL statement> 
Enter fullscreen mode Exit fullscreen mode

Let’s break them down:


πŸ”– sqlc Query Types Explained

Tag Purpose Return Type (Go) Use When...
:one Return exactly one row (Type, error) SELECT that should return a single record
:many Return multiple rows ([]Type, error) SELECT with multiple rows expected
:exec Execute query, no result rows (error) INSERT/UPDATE/DELETE without returning
:execrows Like :exec but also returns rows affected (int64, error) UPDATE/DELETE where you want affected row count
:copyfrom Use COPY FROM PostgreSQL bulk import (int64, error) For high-speed bulk insert

🧠 Examples


βœ… :one

-- name: GetUserByID :one SELECT id, username, email FROM users WHERE id = $1; 
Enter fullscreen mode Exit fullscreen mode

Generates:

func (q *Queries) GetUserByID(ctx context.Context, id int32) (User, error) 
Enter fullscreen mode Exit fullscreen mode

βœ… :many

-- name: ListUsers :many SELECT id, username, email FROM users ORDER BY id DESC LIMIT 10; 
Enter fullscreen mode Exit fullscreen mode

Generates:

func (q *Queries) ListUsers(ctx context.Context) ([]User, error) 
Enter fullscreen mode Exit fullscreen mode

βœ… :exec

-- name: DeleteUser :exec DELETE FROM users WHERE id = $1; 
Enter fullscreen mode Exit fullscreen mode

Generates:

func (q *Queries) DeleteUser(ctx context.Context, id int32) error 
Enter fullscreen mode Exit fullscreen mode

βœ… :execrows

-- name: UpdateEmail :execrows UPDATE users SET email = $2 WHERE id = $1; 
Enter fullscreen mode Exit fullscreen mode

Generates:

func (q *Queries) UpdateEmail(ctx context.Context, id int32, email string) (int64, error) 
Enter fullscreen mode Exit fullscreen mode

βœ… :copyfrom

-- name: CopyUsers :copyfrom COPY users (username, email) FROM STDIN; 
Enter fullscreen mode Exit fullscreen mode

Generates:

func (q *Queries) CopyUsers(ctx context.Context, r io.Reader) (int64, error) 
Enter fullscreen mode Exit fullscreen mode

Used for efficient bulk data import with a CSV reader or similar.


🧠 Summary Cheat Sheet

You want to... Use this
SELECT 1 row :one
SELECT many rows :many
INSERT/UPDATE/DELETE (no rows) :exec
UPDATE and get row count :execrows
COPY FROM for bulk insert :copyfrom

Top comments (0)