<h2>Introdução: Por que GraphQL em Go com gqlgen?</h2>
<p>GraphQL é uma linguagem de query moderna que permite aos clientes solicitar exatamente os dados que precisam, nada mais, nada menos. Quando comparado a REST APIs tradicionais, GraphQL reduz significativamente o over-fetching e under-fetching de dados. Go é uma linguagem compilada, rápida e eficiente, ideal para construir APIs de alta performance. O gqlgen é um gerador de código GraphQL para Go que segue a abordagem <strong>schema-first</strong>, significa que você define primeiro o contrato da sua API no schema GraphQL, e então o gqlgen gera código tipo-seguro para você implementar.</p>
<p>A abordagem schema-first é poderosa porque estabelece um contrato claro entre cliente e servidor antes de qualquer implementação. Você não fica preso a estruturas Go específicas; em vez disso, define o que sua API deve fazer, e o gqlgen cria as interfaces e tipos necessários. Isso torna o desenvolvimento mais organizado, previsível e menos propenso a erros de tipo em tempo de execução. Vou guiá-lo através do processo completo: desde a instalação, passando pela definição do schema, até a implementação dos resolvers com tipagem completa.</p>
<h2>Configuração do Projeto e Instalação</h2>
<h3>Preparando o Ambiente</h3>
<p>Comece criando um novo diretório para seu projeto e inicialize um módulo Go. Você precisará do Go 1.16 ou superior instalado em sua máquina. O gqlgen foi projetado para funcionar com a estrutura padrão de projetos Go, então mantenemos uma organização simples e clara.</p>
<pre><code class="language-bash">mkdir meu-graphql-api
cd meu-graphql-api
go mod init github.com/seu-usuario/meu-graphql-api</code></pre>
<p>Agora instale o gqlgen. A forma recomendada é adicioná-lo como uma dependência indireta através de um arquivo <code>tools.go</code>:</p>
<pre><code class="language-go">// tools.go
//go:build tools
// +build tools
package main
import (
_ "github.com/99designs/gqlgen"
)</code></pre>
<p>Execute <code>go mod tidy</code> para baixar a dependência. Depois, crie o arquivo de configuração:</p>
<pre><code class="language-bash">go run github.com/99designs/gqlgen init</code></pre>
<p>Este comando cria:</p>
<ul>
<li><code>gqlgen.yml</code> — configuração do projeto</li>
<li><code>graph/schema.graphqls</code> — seu schema GraphQL</li>
<li><code>graph/model/models_gen.go</code> — modelos gerados</li>
<li>Pastas de suporte como <code>graph/resolver.go</code></li>
</ul>
<h3>Estrutura do Projeto</h3>
<p>A estrutura final fica assim:</p>
<pre><code>meu-graphql-api/
├── go.mod
├── go.sum
├── gqlgen.yml
├── graph/
│ ├── schema.graphqls
│ ├── model/
│ │ └── models_gen.go
│ ├── resolver.go
│ └── schema.resolvers.go
├── server.go
└── tools.go</code></pre>
<p>O arquivo <code>gqlgen.yml</code> é o coração da configuração. Ele mapeia seus tipos GraphQL para tipos Go e define como o código é gerado. Por padrão, ele está bem configurado, mas você pode customizá-lo conforme necessário para controlar namespaces de pacotes, caminhos de saída e comportamentos de geração.</p>
<h2>Definindo o Schema GraphQL (Schema-First)</h2>
<h3>Conceitos Fundamentais do Schema</h3>
<p>Um schema GraphQL define todos os tipos, queries, mutations e subscriptions disponíveis em sua API. Ele é um contrato explícito entre cliente e servidor. Quando você trabalha com schema-first, este é o ponto de partida: você escreve o schema antes do código Go, e o gqlgen infere quais tipos e funções você precisa implementar.</p>
<p>Abra o arquivo <code>graph/schema.graphqls</code> e defina um schema simples mas realista. Vou criar um exemplo de um sistema de blog:</p>
<pre><code class="language-graphql">type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: String!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Query {
post(id: ID!): Post
user(id: ID!): User
allPosts: [Post!]!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post
}
input CreatePostInput {
title: String!
content: String!
authorID: ID!
}
input UpdatePostInput {
title: String
content: String
}</code></pre>
<p>Este schema define dois tipos principais (<code>Post</code> e <code>User</code>), as operações de leitura (<code>Query</code>), as operações de escrita (<code>Mutation</code>), e dois tipos de entrada (<code>CreatePostInput</code> e <code>UpdatePostInput</code>). A exclamação <code>!</code> indica que o campo é obrigatório (não-nulo). Colchetes <code>[]</code> indicam listas.</p>
<h3>Gerando Código a partir do Schema</h3>
<p>Após definir o schema, execute:</p>
<pre><code class="language-bash">go run github.com/99designs/gqlgen generate</code></pre>
<p>O gqlgen analisa o schema, verifica as dependências e gera código tipo-seguro. Ele cria:</p>
<ul>
<li>Interfaces Go para todos os tipos (<code>Mutation</code>, <code>Query</code>, <code>Post</code>, etc.)</li>
<li>Modelos de dados estruturados</li>
<li>Um arquivo <code>schema.resolvers.go</code> onde você implementa os resolvers</li>
</ul>
<p>Este processo elimina a necessidade de você definir manualmente as estruturas Go correspondentes a cada tipo GraphQL. O código gerado é consistente, bem-tipado e segue as melhores práticas de Go.</p>
<h2>Implementando Resolvers Tipados</h2>
<h3>O que é um Resolver?</h3>
<p>Um resolver é uma função que implementa a lógica de negócio para um campo específico em seu schema GraphQL. Para cada campo que não é um tipo primitivo de uma única fonte de dados, você precisa de um resolver. No schema anterior, <code>Query.post</code>, <code>Query.user</code>, <code>Mutation.createPost</code> e o campo <code>Post.author</code> são campos que precisam de resolvers porque não podem ser diretamente satisfeitos a partir de uma única fonte de dados.</p>
<p>Quando você executa <code>go run github.com/99designs/gqlgen generate</code> pela primeira vez, o gqlgen cria um arquivo <code>schema.resolvers.go</code> com stubs (esqueletos) de todos os resolvers. Você preencherá estes stubs com sua lógica de negócio.</p>
<h3>Implementando Resolvers de Query</h3>
<p>Abra <code>graph/schema.resolvers.go</code> e implemente os resolvers de query. Vou criar uma implementação simples com dados em memória:</p>
<pre><code class="language-go">package graph
import (
"context"
"fmt"
"github.com/seu-usuario/meu-graphql-api/graph/model"
)
// Dados em memória para este exemplo
var (
users = map[string]*model.User{
"user1": {
ID: "user1",
Name: "João Silva",
Email: "joao@example.com",
},
}
posts = map[string]*model.Post{
"post1": {
ID: "post1",
Title: "Introdução a GraphQL",
Content: "GraphQL é uma linguagem de query...",
AuthorID: "user1",
CreatedAt: "2024-01-15",
},
}
)
// Post resolve o campo Query.post
func (r queryResolver) Post(ctx context.Context, id string) (model.Post, error) {
post, exists := posts[id]
if !exists {
return nil, fmt.Errorf("post não encontrado")
}
return post, nil
}
// User resolve o campo Query.user
func (r queryResolver) User(ctx context.Context, id string) (model.User, error) {
user, exists := users[id]
if !exists {
return nil, fmt.Errorf("usuário não encontrado")
}
return user, nil
}
// AllPosts resolve o campo Query.allPosts
func (r queryResolver) AllPosts(ctx context.Context) ([]model.Post, error) {
var result []*model.Post
for _, post := range posts {
result = append(result, post)
}
return result, nil
}</code></pre>
<p>Note que cada resolver recebe <code>context.Context</code> como primeiro parâmetro. Isso permite rastreamento, cancelamento de operações de longa duração e passagem de valores entre middlewares. Os retornos seguem o padrão Go: resultado, erro.</p>
<h3>Resolvendo Campos Aninhados</h3>
<p>GraphQL é especial porque permite campos aninhados. Se um cliente solicitar:</p>
<pre><code class="language-graphql">{
post(id: "post1") {
title
author {
name
}
}
}</code></pre>
<p>O campo <code>author</code> dentro de <code>Post</code> também precisa de um resolver. Embora nosso modelo <code>Post</code> tenha apenas <code>AuthorID</code>, o schema exige um campo <code>Author</code> de tipo <code>User</code>. Vamos implementar:</p>
<pre><code class="language-go">// Author resolve o campo Post.author
func (r postResolver) Author(ctx context.Context, obj model.Post) (*model.User, error) {
user, exists := users[obj.AuthorID]
if !exists {
return nil, fmt.Errorf("autor não encontrado")
}
return user, nil
}
// Posts resolve o campo User.posts (usuário possui múltiplos posts)
func (r userResolver) Posts(ctx context.Context, obj model.User) ([]*model.Post, error) {
var result []*model.Post
for _, post := range posts {
if post.AuthorID == obj.ID {
result = append(result, post)
}
}
return result, nil
}</code></pre>
<p>Este é um ponto crucial: resolvers de campos aninhados recebem o objeto pai como parâmetro (<code>obj <em>model.Post</code>, <code>obj </em>model.User</code>). Isso permite resolver o campo usando dados do pai, mantendo a grafo de dados coeso.</p>
<h3>Implementando Mutations</h3>
<p>Mutations alteram dados. Implementamos seguindo o mesmo padrão:</p>
<pre><code class="language-go">// CreatePost resolve o campo Mutation.createPost
func (r *mutationResolver) CreatePost(
ctx context.Context,
input model.CreatePostInput,
) (*model.Post, error) {
// Gerar um novo ID (em produção, use UUID ou banco de dados)
newID := fmt.Sprintf("post%d", len(posts)+1)
newPost := &model.Post{
ID: newID,
Title: input.Title,
Content: input.Content,
AuthorID: input.AuthorID,
CreatedAt: "2024-01-15", // Em produção, use time.Now()
}
posts[newID] = newPost
return newPost, nil
}
// UpdatePost resolve o campo Mutation.updatePost
func (r *mutationResolver) UpdatePost(
ctx context.Context,
id string,
input model.UpdatePostInput,
) (*model.Post, error) {
post, exists := posts[id]
if !exists {
return nil, fmt.Errorf("post não encontrado")
}
if input.Title != nil {
post.Title = *input.Title
}
if input.Content != nil {
post.Content = *input.Content
}
return post, nil
}</code></pre>
<p>Note que <code>UpdatePostInput</code> utiliza ponteiros (<code>*string</code>) porque os campos são opcionais. Um ponteiro <code>nil</code> significa que o campo não foi fornecido, então não deve ser atualizado.</p>
<h2>Executando e Testando sua API GraphQL</h2>
<h3>Configurando o Servidor HTTP</h3>
<p>Crie um arquivo <code>server.go</code> na raiz do projeto que inicia o servidor HTTP:</p>
<pre><code class="language-go">package main
import (
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/seu-usuario/meu-graphql-api/graph"
"github.com/seu-usuario/meu-graphql-api/graph/generated"
)
const defaultPort = "8080"
func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{
Resolvers: &graph.Resolver{},
}))
http.Handle("/query", srv)
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}</code></pre>
<p>Este código:</p>
<ol>
<li>Cria um <code>ExecutableSchema</code> — a representação interna do gqlgen de seu schema e resolvers</li>
<li>Monta o handler de GraphQL em <code>/query</code></li>
<li>Monta o GraphQL Playground (ferramenta visual para testar queries) em <code>/</code></li>
<li>Inicia o servidor na porta 8080</li>
</ol>
<h3>Testando com GraphQL Playground</h3>
<p>Execute o servidor:</p>
<pre><code class="language-bash">go run server.go</code></pre>
<p>Abra http://localhost:8080 em seu navegador. Você verá o GraphQL Playground. Teste uma query simples:</p>
<pre><code class="language-graphql">query {
allPosts {
id
title
author {
name
}
}
}</code></pre>
<p>Teste uma mutation:</p>
<pre><code class="language-graphql">mutation {
createPost(input: {
title: "Novo Post"
content: "Conteúdo do novo post"
authorID: "user1"
}) {
id
title
createdAt
}
}</code></pre>
<p>O GraphQL Playground fornece autocompletar (Ctrl+Space), validação em tempo real e documentação integrada do seu schema. Esta é uma ferramenta poderosa para desenvolvimento.</p>
<h3>Adicionando Validação e Tratamento de Erros</h3>
<p>Em produção, adicione validação robusta. Modifique seu <code>CreatePost</code>:</p>
<pre><code class="language-go">func (r *mutationResolver) CreatePost(
ctx context.Context,
input model.CreatePostInput,
) (*model.Post, error) {
// Validar entrada
if input.Title == "" {
return nil, fmt.Errorf("título não pode estar vazio")
}
if len(input.Title) > 200 {
return nil, fmt.Errorf("título não pode exceder 200 caracteres")
}
if input.Content == "" {
return nil, fmt.Errorf("conteúdo não pode estar vazio")
}
// Verificar se o autor existe
if _, exists := users[input.AuthorID]; !exists {
return nil, fmt.Errorf("autor com ID %s não encontrado", input.AuthorID)
}
newID := fmt.Sprintf("post%d", len(posts)+1)
newPost := &model.Post{
ID: newID,
Title: input.Title,
Content: input.Content,
AuthorID: input.AuthorID,
CreatedAt: "2024-01-15",
}
posts[newID] = newPost
return newPost, nil
}</code></pre>
<p>Erros retornados dos resolvers são automaticamente formatados como erros GraphQL e retornados ao cliente com status HTTP 200 e um campo <code>errors</code> na resposta JSON.</p>
<h2>Padrões Avançados e Boas Práticas</h2>
<h3>Injeção de Dependência</h3>
<p>Em aplicações reais, seus resolvers precisam acessar banco de dados, cache, APIs externas, etc. Use injeção de dependência através do <code>Resolver</code> raiz:</p>
<pre><code class="language-go">// graph/resolver.go
package graph
import (
"database/sql"
"log"
)
type Resolver struct {
db *sql.DB
log *log.Logger
}
func NewResolver(db sql.DB, logger log.Logger) *Resolver {
return &Resolver{
db: db,
log: logger,
}
}</code></pre>
<p>Agora seus resolvers podem acessar <code>r.db</code> e <code>r.log</code>. No <code>server.go</code>:</p>
<pre><code class="language-go">func main() {
// ... setup db
resolver := graph.NewResolver(db, log.New(os.Stdout, "", log.LstdFlags))
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{
Resolvers: resolver,
}))
// ...
}</code></pre>
<h3>DataLoader para Evitar N+1 Queries</h3>
<p>Um problema comum: quando você resolve <code>User.posts</code> para múltiplos usuários, executa uma query por usuário (problema N+1). DataLoader agrupa requisições:</p>
<p>Instale: <code>go get github.com/graph-gophers/dataloader/v7</code></p>
<pre><code class="language-go">import "github.com/graph-gophers/dataloader/v7"
type loaders struct {
postsByAuthorID *dataloader.Loader
}
// Criar loader na inicialização
func NewLoaders(db sql.DB) loaders {
return &loaders{
postsByAuthorID: dataloader.NewBatchedLoader(
func(ctx context.Context, ids []string) []*dataloader.Result {
// Buscar todos os posts destes autores em uma única query
results := make([]*dataloader.Result, len(ids))
// ... implementar lógica batch
return results
},
),
}
}</code></pre>
<p>Use no resolver:</p>
<pre><code class="language-go">func (r userResolver) Posts(ctx context.Context, obj model.User) ([]*model.Post, error) {
// Usar loader em vez de query individual
return r.postsByAuthorIDLoader.Load(ctx, obj.ID)
}</code></pre>
<h3>Middleware para Logging, Autenticação e Rate Limiting</h3>
<p>O gqlgen permite adicionar middleware no handler:</p>
<pre><code class="language-go">srv := handler.NewDefaultServer(schema)
// Middleware de logging
srv.Use(&logging.Middleware{})
// Middleware de autenticação
srv.Use(&auth.Middleware{})
// Middleware de rate limiting
srv.Use(&ratelimit.Middleware{})</code></pre>
<p>Você pode implementar estes middlewares usando a interface <code>graphql.OperationMiddleware</code> ou <code>graphql.ResponseMiddleware</code> fornecida pelo gqlgen.</p>
<h2>Conclusão</h2>
<p>Você aprendeu como utilizar o gqlgen para construir APIs GraphQL tipadas e robustas em Go seguindo a abordagem schema-first. Primeiro, você define o contrato da API no schema GraphQL, o que fornece clareza e documentação automática. Segundo, o gqlgen gera código tipo-seguro baseado no schema, eliminando classes inteiras de erros de tipo e mantendo cliente e servidor sincronizados. Terceiro, implementar resolvers é direto porque cada função tem uma assinatura bem-definida, parâmetros explícitos e retornos seguindo padrões Go idiomáticos — você sabe exatamente o que implementar e o resultado é código limpo, testável e manutenível.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://graphql.org/" target="_blank" rel="noopener noreferrer">GraphQL Official Documentation</a></li>
<li><a href="https://gqlgen.com/" target="_blank" rel="noopener noreferrer">gqlgen Official Documentation</a></li>
<li><a href="https://gqlgen.com/docs/getting-started/" target="_blank" rel="noopener noreferrer">Go GraphQL Best Practices</a></li>
<li><a href="https://github.com/graph-gophers/dataloader" target="_blank" rel="noopener noreferrer">Graph-Gophers DataLoader</a></li>
<li><a href="https://golang.org/ref/spec" target="_blank" rel="noopener noreferrer">The Go Programming Language Specification</a></li>
</ul>
<p><!-- FIM --></p>