Introduction

SQLC is a tool that generates type-safe Go code from SQL queries. It bridges the gap between SQL and Go by allowing developers to write raw SQL queries, and automatically generating the corresponding Go code.

The go code generated by the SQL queries is validated at compile time, reducing runtime errors and should improving code maintainability.

SQL Files and Migrations

Before we start with SQLC, we’ll first need to set up the SQL files and migrations.

With migration files, you can manage your database schema changes and versioning. And SQLC is compatible with several migration tools.

Fell free to choose the one you prefer. As for my particular case I chose dbmate because it’s written in Go, it’s simple to set up with a useful CLI, and has a simple one file per migration system.

Installation

To install dbmate, you can use the following command:

go install github.com/amacneil/dbmate@latest

To create a migration use:

dbmate new create_authors_table

Then you can run the migration on a test database running in the background. Make sure you have set the DATABASE_URL environment variable for the tool to connect to it and apply the migration. Then run the migration in the root directory where the migration folder in db/migrations is located:

dbmate up

This will run all migrations that have not been run yet.

Example Migration File with dbmate

Create a migration file in the db/migrations directory:

-- db/migrations/202504020000_create_authors_table.sql
-- migrate:up
CREATE TABLE IF NOT EXISTS authors (
   id   BIGSERIAL PRIMARY KEY,
   name text NOT NULL,
   bio  text
);

-- migrate:down
DROP TABLE IF EXISTS authors;

Example Query

Create a SQL query file in the db directory called query.sql to fetch an author by ID:

-- db/query.sql
-- name: GetAuthor :one
SELECT *
FROM authors
WHERE id = $1;

This will be used to generate the Go code. We could also have a folder with queries organized by entity or feature in different files.

Using SQLC

Installation

To get started with SQLC, you need to install it. You can do this using the following command:

go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest

I am basing this article on the official documentation for SQLC with PostgreSQL.

Configuration with sqlc.yaml

Create a sqlc.yaml file in the root of your project to configure SQLC. This file specifies the database schema, queries, and the output directory for the generated Go code.

version: "2"
sql:
  - engine: "postgresql"
    queries: "../db/query.sql"
    schema: "../db/migrations" # Or use 
    gen:
      go:
        package: "sqlc"
        out: "../db/sqlc"
        sql_package: "pgx/v5"
        output_batch_file_name: "batch.gen.go"
        output_db_file_name: "db.gen.go"
        output_models_file_name: "models.gen.go"

We are using the migrations file to build the database schema, but you can also test it out with a single file schema: "../db/schema.sql" with all the schema definitions. If you are planning to go into production, I’d advise using migrations to keep track of the changes.

Using the tools pattern

The tools.go file is a common pattern in Go projects to manage dependencies and tools. You can have multiple generate command and tool dependency in the same file.

//go:build tools
package tools

import (
	_ "github.com/sqlc-dev/sqlc/cmd/sqlc"
)

// SQL code generation
//go:generate sqlc generate

In our case it will ensure that the sqlc module is installed and available for code generation. Now you can:

go generate ./...
# Or directly sqlc generate

The script will generate the Go code.

Generated code

Assuming you have this folder structure, that should match what is specified in the sqlc.yaml file:

.
├── db/
   ├── migrations/
      └── 202504020000_create_authors_table.sql
   └── query.sql
├── internal/
   └── ...
├── tools.go
└── sqlc.yaml

The generated Go code should be in the db/sqlc directory with the .gen.go extension. I use that extension to differentiate the generated code from the handwritten code.

db/sqlc/
    ├── db.gen.go           # High level interface and DB functions
    ├── models.gen.go       # Database models
    └── queries.gen.go      # Go code for the queries

The generated code from the sql queries is already fully typed and tested to match your query. This is far more reliable than using AI to build the go code, you just need to use it to write the SQL queries. 🙃 Just make sure that queries does what you expect.

Generated Code

Connect to the Database

Let’s dive into the code that gets generated by SQLC. Firs in db.gen.go, you will find the DBTX interface with the basic db commands, and the Queries struct which will hold all the queries as go functions:

// db.gen.go
package sqlc

type DBTX interface {
	Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
	Query(context.Context, string, ...interface{}) (pgx.Rows, error)
	QueryRow(context.Context, string, ...interface{}) pgx.Row
}

func New(db DBTX) *Queries {
	return &Queries{db: db}
}

To use it with your database connection, you can do the following in a simple script to test it out. We are using pgx as the PostgresSQL database driver, it was specified in the sqlc.yaml file as well.

package main

import (
	"context"
	"app/db/sqlc"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
)

func main() {
    ctx := context.Background()
    conn, err := pgx.Connect(ctx, "postgres://username:password@localhost:5432/dbname")
    if err != nil {
        return err
    }
    defer conn.Close(ctx)

    queries := sqlc.New(conn)
}

Execute a Query

Now with the queries object, you can call the generated functions to interact with the database. Let’s look at the generated code for the GetAuthor query:

// queries.gen.go
const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio, uuid FROM authors WHERE id = $1`

func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
    row := q.db.QueryRow(ctx, getAuthor, id)
    var i Author
    err := row.Scan(
        &i.ID,
        &i.Name,
        &i.Bio,
        &i.Uuid,
    )
    return i, err
}

It keeps in a variable the actual SQL query and the function to call it and return an object of the type Author.

// models.gen.go
type Author struct {
    ID   int64
    Name string
    Bio  pgtype.Text
    Uuid pgtype.UUID
}

The generated object is using the pgtype package to handle the Postgres specific types. The type can be overridden to a certain level in the sqlc.yaml file.

But it’s recommended to convert those type to a “business logic” type, so that you don’t propagate the database types in your code. Let’s see how it can be used with our previously created queries object:

author, err := queries.GetAuthor(ctx, 1)

This will return the author with the ID 1 from the database, or an error saying no row was found if the author doesn’t exist.

That’s it for the basics with SQLC, hopefully that’s enough to get you started with it.