Go

O que Todo Dev Deve Saber sobre SQLC em Go: Gerando Código Tipado a partir de Queries SQL

12 min de leitura

O que Todo Dev Deve Saber sobre SQLC em Go: Gerando Código Tipado a partir de Queries SQL

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,

<h2>O que é SQLC e Por que Você Deveria Usar</h2>

<p>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 &quot;magia&quot; das ORMs tradicionais.</p>

<p>A principal vantagem é a <strong>segurança de tipo em tempo de compilação</strong>. 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.</p>

<h3>Por que não apenas usar uma ORM?</h3>

<p>ORMs como GORM são convenientes, mas abstraem SQL demais. Você perde controle fino sobre suas queries, performance fica imprevisível, e aprender a &quot;linguagem&quot; 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.</p>

<h2>Instalação e Configuração Inicial</h2>

<h3>Instalando o SQLC</h3>

<p>Primeiro, instale o SQLC em sua máquina. Visite <a href="https://docs.sqlc.dev/en/latest/overview/install.html" target="_blank" rel="noopener noreferrer">https://docs.sqlc.dev/en/latest/overview/install.html</a> para o método mais recente. Para a maioria dos usuários:</p>

<pre><code class="language-bash">go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest</code></pre>

<p>Verifique se está funcionando:</p>

<pre><code class="language-bash">sqlc version</code></pre>

<h3>Configurando seu projeto</h3>

<p>Crie um arquivo <code>sqlc.yaml</code> na raiz do seu projeto:</p>

<pre><code class="language-yaml">version: &quot;2&quot;

sql:

  • engine: &quot;postgres&quot;

queries: &quot;./queries&quot;

schema: &quot;./schema&quot;

gen:

go:

out: &quot;./internal/db&quot;

package: &quot;db&quot;</code></pre>

<p>Esta configuração diz ao SQLC: leia as migrations em <code>./schema</code>, leia as queries em <code>./queries</code> e gere código Go em <code>./internal/db</code> com o package <code>db</code>. Crie os diretórios necessários:</p>

<pre><code class="language-bash">mkdir -p queries schema internal/db</code></pre>

<h3>Esquema do banco de dados</h3>

<p>Crie <code>schema/001_initial.sql</code> com uma estrutura de exemplo:</p>

<pre><code class="language-sql">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

);</code></pre>

<p>Este é apenas um exemplo de schema que usaremos nas próximas seções.</p>

<h2>Escrevendo Queries e Gerando Código</h2>

<h3>Estruturando suas queries</h3>

<p>Crie <code>queries/users.sql</code>. SQLC usa comentários especiais para identificar tipos de operação:</p>

<pre><code class="language-sql">-- 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;</code></pre>

<p>Os comentários especiais definem:</p>

<ul>

<li><code>-- name: NomeDaFuncao</code>: o nome da função que será gerada</li>

<li><code>:one</code>: retorna uma única linha (um struct)</li>

<li><code>:many</code>: retorna múltiplas linhas (slice de structs)</li>

<li><code>:exec</code>: apenas executa (DELETE, UPDATE sem RETURNING)</li>

</ul>

<p>Crie também <code>queries/posts.sql</code>:</p>

<pre><code class="language-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;</code></pre>

<h3>Gerando o código</h3>

<p>Execute:</p>

<pre><code class="language-bash">sqlc generate</code></pre>

<p>Se tudo estiver correto, você verá novos arquivos em <code>internal/db/</code>. Examine-os:</p>

<pre><code class="language-go">// internal/db/models.go

package db

import (

&quot;time&quot;

)

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

}</code></pre>

<pre><code class="language-go">// internal/db/users.sql.go (parcial)

package db

import (

&quot;context&quot;

)

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(&amp;i.ID, &amp;i.Name, &amp;i.Email, &amp;i.CreatedAt)

return i, err

}</code></pre>

<p>Perceba: <strong>código seguro, tipado e sem reflexão</strong>. Os parâmetros e retornos são verificados em tempo de compilação.</p>

<h2>Usando SQLC em uma Aplicação Real</h2>

<h3>Configurando a conexão com o banco</h3>

<p>Crie <code>main.go</code>:</p>

<pre><code class="language-go">package main

import (

&quot;context&quot;

&quot;database/sql&quot;

&quot;fmt&quot;

&quot;log&quot;

_ &quot;github.com/lib/pq&quot;

&quot;yourmodule/internal/db&quot;

)

func main() {

// Substitua pelos seus dados de conexão

connStr := &quot;postgres://user:password@localhost:5432/yourdb?sslmode=disable&quot;

sqldb, err := sql.Open(&quot;postgres&quot;, 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: &quot;João Silva&quot;,

Email: &quot;joao@example.com&quot;,

})

if err != nil {

log.Fatal(err)

}

fmt.Printf(&quot;Usuário criado: %d - %s\n&quot;, user.ID, user.Name)

// Buscar um usuário

fetchedUser, err := q.GetUser(ctx, user.ID)

if err != nil {

log.Fatal(err)

}

fmt.Printf(&quot;Usuário encontrado: %s (%s)\n&quot;, fetchedUser.Name, fetchedUser.Email)

// Listar todos os usuários

users, err := q.ListUsers(ctx)

if err != nil {

log.Fatal(err)

}

fmt.Printf(&quot;Total de usuários: %d\n&quot;, len(users))

for _, u := range users {

fmt.Printf(&quot; - %s: %s\n&quot;, u.Name, u.Email)

}

// Criar um post

post, err := q.CreatePost(ctx, db.CreatePostParams{

UserID: user.ID,

Title: &quot;Meu Primeiro Post&quot;,

Content: &quot;Este é o conteúdo do meu primeiro post com SQLC!&quot;,

})

if err != nil {

log.Fatal(err)

}

fmt.Printf(&quot;Post criado: %d\n&quot;, post.ID)

// Buscar posts do usuário

posts, err := q.GetPostsByUserID(ctx, user.ID)

if err != nil {

log.Fatal(err)

}

fmt.Printf(&quot;Posts do usuário: %d\n&quot;, len(posts))

for _, p := range posts {

fmt.Printf(&quot; - %s\n&quot;, p.Title)

}

}</code></pre>

<h3>Tratamento de erros com SQLC</h3>

<p>SQLC retorna erros padrão do Go. Para diferenciar entre &quot;não encontrado&quot; e outros erros:</p>

<pre><code class="language-go">import &quot;database/sql&quot;

user, err := q.GetUser(ctx, 999) // ID que não existe

if err != nil {

if err == sql.ErrNoRows {

fmt.Println(&quot;Usuário não encontrado&quot;)

} else {

log.Fatal(err)

}

}</code></pre>

<h2>Recursos Avançados e Boas Práticas</h2>

<h3>Queries parametrizadas com IN</h3>

<p>Para queries com múltiplos IDs, SQLC suporta slices:</p>

<pre><code class="language-sql">-- name: GetUsersByIDs :many

SELECT id, name, email, created_at FROM users WHERE id = ANY($1);</code></pre>

<p>Chamado assim:</p>

<pre><code class="language-go">users, err := q.GetUsersByIDs(ctx, []int32{1, 2, 3})</code></pre>

<h3>Transações</h3>

<p>SQLC funciona perfeitamente com transações padrão do Go:</p>

<pre><code class="language-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: &quot;Maria&quot;,

Email: &quot;maria@example.com&quot;,

})

if err != nil {

return err

}

post, err := qtx.CreatePost(ctx, db.CreatePostParams{

UserID: user.ID,

Title: &quot;Post dentro de transação&quot;,

Content: &quot;Conteúdo&quot;,

})

if err != nil {

return err

}

return tx.Commit()</code></pre>

<h3>Estruturando seu projeto</h3>

<p>Uma estrutura bem organizada com SQLC:</p>

<pre><code>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</code></pre>

<p>Mantenha suas queries organizadas por domínio e não misture lógica Go com lógica SQL.</p>

<h2>Conclusão</h2>

<p>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: <strong>(1) SQLC gera código tipado a partir de SQL, eliminando erros de mapeamento em tempo de compilação</strong>; <strong>(2) o fluxo é simples — defina schema, escreva queries com comentários especiais, execute <code>sqlc generate</code></strong>; <strong>(3) o código gerado é Go puro, não há abstrações mágicas, resultando em performance previsível e fácil debugging</strong>.</p>

<p>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).</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.sqlc.dev/" target="_blank" rel="noopener noreferrer">SQLC Official Documentation</a></li>

<li><a href="https://github.com/sqlc-dev/sqlc" target="_blank" rel="noopener noreferrer">SQLC GitHub Repository</a></li>

<li><a href="https://docs.sqlc.dev/en/latest/tutorials/getting-started.html" target="_blank" rel="noopener noreferrer">Getting Started with SQLC - Official Tutorial</a></li>

<li><a href="https://pkg.go.dev/database/sql" target="_blank" rel="noopener noreferrer">Go database/sql Package Documentation</a></li>

<li><a href="https://github.com/lib/pq" target="_blank" rel="noopener noreferrer">PostgreSQL Go Driver - pq Documentation</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

GORM em Go: ORM, Migrations e Relacionamentos na Prática na Prática
GORM em Go: ORM, Migrations e Relacionamentos na Prática na Prática

Introdução ao GORM e por que ele é indispensável GORM é a biblioteca ORM (Obj...

Middleware de Autenticação, Logging e Rate Limiting em Go na Prática
Middleware de Autenticação, Logging e Rate Limiting em Go na Prática

Introdução: Por que Middleware Importa em Go Middleware é um padrão fundament...

Profiling de Memória em Go: pprof, Heap Dumps e Otimizações: Do Básico ao Avançado
Profiling de Memória em Go: pprof, Heap Dumps e Otimizações: Do Básico ao Avançado

Introdução ao Profiling de Memória em Go Profiling é a análise sistemática de...