O que é SQLC e Por que Você Deveria Usar
SQLC é uma ferramenta que gera código Go tipado automaticamente a partir de suas queries SQL. Em vez de escrever manualmente structs, funções de mapeamento e tratamento de erros, você define suas queries SQL e o SQLC cuida de gerar todo o código repetitivo de forma segura e eficiente. Isso reduz bugs, melhora a manutenibilidade e elimina a famosa "magia" das ORMs tradicionais.
A principal vantagem é a segurança de tipo em tempo de compilação. Se você refatorar uma coluna no banco de dados ou mudar o tipo de um parâmetro, o SQLC vai gerar código que não compila até que você atualize suas queries — nada de descobrir erros em produção. Além disso, SQLC não interpreta SQL em tempo de execução como ORMs fazem; gera código Go puro, resultando em performance superior e sem overhead de reflexão.
Por que não apenas usar uma ORM?
ORMs como GORM são convenientes, mas abstraem SQL demais. Você perde controle fino sobre suas queries, performance fica imprevisível, e aprender a "linguagem" da ORM adiciona complexidade. SQLC pega o melhor dos dois mundos: você escreve SQL direto (controle total) e recebe código Go seguro e tipado automaticamente.
Instalação e Configuração Inicial
Instalando o SQLC
Primeiro, instale o SQLC em sua máquina. Visite https://docs.sqlc.dev/en/latest/overview/install.html para o método mais recente. Para a maioria dos usuários:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
Verifique se está funcionando:
sqlc version
Configurando seu projeto
Crie um arquivo sqlc.yaml na raiz do seu projeto:
version: "2"
sql:
- engine: "postgres"
queries: "./queries"
schema: "./schema"
gen:
go:
out: "./internal/db"
package: "db"
Esta configuração diz ao SQLC: leia as migrations em ./schema, leia as queries em ./queries e gere código Go em ./internal/db com o package db. Crie os diretórios necessários:
mkdir -p queries schema internal/db
Esquema do banco de dados
Crie schema/001_initial.sql com uma estrutura de exemplo:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id),
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Este é apenas um exemplo de schema que usaremos nas próximas seções.
Escrevendo Queries e Gerando Código
Estruturando suas queries
Crie queries/users.sql. SQLC usa comentários especiais para identificar tipos de operação:
-- name: GetUser :one
SELECT id, name, email, created_at FROM users WHERE id = $1;
-- name: ListUsers :many
SELECT id, name, email, created_at FROM users ORDER BY created_at DESC;
-- name: CreateUser :one
INSERT INTO users (name, email) VALUES ($1, $2)
RETURNING id, name, email, created_at;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
-- name: UpdateUser :exec
UPDATE users SET name = $2, email = $3 WHERE id = $1;
Os comentários especiais definem:
- -- name: NomeDaFuncao: o nome da função que será gerada
- :one: retorna uma única linha (um struct)
- :many: retorna múltiplas linhas (slice de structs)
- :exec: apenas executa (DELETE, UPDATE sem RETURNING)
Crie também queries/posts.sql:
-- name: CreatePost :one
INSERT INTO posts (user_id, title, content) VALUES ($1, $2, $3)
RETURNING id, user_id, title, content, created_at;
-- name: GetPostsByUserID :many
SELECT id, user_id, title, content, created_at FROM posts
WHERE user_id = $1 ORDER BY created_at DESC;
-- name: GetPost :one
SELECT id, user_id, title, content, created_at FROM posts WHERE id = $1;
Gerando o código
Execute:
sqlc generate
Se tudo estiver correto, você verá novos arquivos em internal/db/. Examine-os:
// internal/db/models.go
package db
import (
"time"
)
type User struct {
ID int32
Name string
Email string
CreatedAt time.Time
}
type Post struct {
ID int32
UserID int32
Title string
Content string
CreatedAt time.Time
}
// internal/db/users.sql.go (parcial)
package db
import (
"context"
)
const getUser = `-- name: GetUser :one
SELECT id, name, email, created_at FROM users WHERE id = $1
`
func (q *Queries) GetUser(ctx context.Context, id int32) (User, error) {
row := q.db.QueryRowContext(ctx, getUser, id)
var i User
err := row.Scan(&i.ID, &i.Name, &i.Email, &i.CreatedAt)
return i, err
}
Perceba: código seguro, tipado e sem reflexão. Os parâmetros e retornos são verificados em tempo de compilação.
Usando SQLC em uma Aplicação Real
Configurando a conexão com o banco
Crie main.go:
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
"yourmodule/internal/db"
)
func main() {
// Substitua pelos seus dados de conexão
connStr := "postgres://user:password@localhost:5432/yourdb?sslmode=disable"
sqldb, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
defer sqldb.Close()
// Testa a conexão
if err := sqldb.Ping(); err != nil {
log.Fatal(err)
}
// Cria a instância de Queries
queries := db.New(sqldb)
// Seu código aqui
runExamples(context.Background(), queries)
}
func runExamples(ctx context.Context, q *db.Queries) {
// Criar um usuário
user, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "João Silva",
Email: "joao@example.com",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Usuário criado: %d - %s\n", user.ID, user.Name)
// Buscar um usuário
fetchedUser, err := q.GetUser(ctx, user.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Usuário encontrado: %s (%s)\n", fetchedUser.Name, fetchedUser.Email)
// Listar todos os usuários
users, err := q.ListUsers(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total de usuários: %d\n", len(users))
for _, u := range users {
fmt.Printf(" - %s: %s\n", u.Name, u.Email)
}
// Criar um post
post, err := q.CreatePost(ctx, db.CreatePostParams{
UserID: user.ID,
Title: "Meu Primeiro Post",
Content: "Este é o conteúdo do meu primeiro post com SQLC!",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Post criado: %d\n", post.ID)
// Buscar posts do usuário
posts, err := q.GetPostsByUserID(ctx, user.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Posts do usuário: %d\n", len(posts))
for _, p := range posts {
fmt.Printf(" - %s\n", p.Title)
}
}
Tratamento de erros com SQLC
SQLC retorna erros padrão do Go. Para diferenciar entre "não encontrado" e outros erros:
import "database/sql"
user, err := q.GetUser(ctx, 999) // ID que não existe
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("Usuário não encontrado")
} else {
log.Fatal(err)
}
}
Recursos Avançados e Boas Práticas
Queries parametrizadas com IN
Para queries com múltiplos IDs, SQLC suporta slices:
-- name: GetUsersByIDs :many
SELECT id, name, email, created_at FROM users WHERE id = ANY($1);
Chamado assim:
users, err := q.GetUsersByIDs(ctx, []int32{1, 2, 3})
Transações
SQLC funciona perfeitamente com transações padrão do Go:
tx, err := sqldb.BeginTx(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer tx.Rollback()
// Cria uma instância de Queries com a transação
qtx := q.WithTx(tx)
user, err := qtx.CreateUser(ctx, db.CreateUserParams{
Name: "Maria",
Email: "maria@example.com",
})
if err != nil {
return err
}
post, err := qtx.CreatePost(ctx, db.CreatePostParams{
UserID: user.ID,
Title: "Post dentro de transação",
Content: "Conteúdo",
})
if err != nil {
return err
}
return tx.Commit()
Estruturando seu projeto
Uma estrutura bem organizada com SQLC:
projeto/
├── main.go
├── sqlc.yaml
├── go.mod
├── go.sum
├── schema/
│ └── 001_initial.sql
├── queries/
│ ├── users.sql
│ └── posts.sql
└── internal/
├── db/
│ ├── models.go
│ ├── users.sql.go
│ └── posts.sql.go
└── handlers/
└── user_handler.go
Mantenha suas queries organizadas por domínio e não misture lógica Go com lógica SQL.
Conclusão
SQLC resolve um dos maiores problemas na programação Go: gerar código de acesso a dados que é seguro, eficiente e sem boilerplate. Você ganhou três aprendizados principais neste artigo: (1) SQLC gera código tipado a partir de SQL, eliminando erros de mapeamento em tempo de compilação; (2) o fluxo é simples — defina schema, escreva queries com comentários especiais, execute sqlc generate; (3) o código gerado é Go puro, não há abstrações mágicas, resultando em performance previsível e fácil debugging.
Use SQLC quando você quer controle fino sobre SQL, performance máxima e segurança de tipo. Não use quando precisar de migrations automáticas ou quando trabalhar com vários bancos de dados diferentes na mesma aplicação (embora seja possível com configuração extra).