Go Admin

O que Todo Dev Deve Saber sobre Documentação de APIs Go com Swagger e swaggo Já leu

Entendendo APIs e a Importância da Documentação Uma API (Application Programming Interface) é o contrato entre seu servidor e seus clientes. Quando você cria uma API em Go, está definindo endpoints, métodos HTTP, formatos de requisição e resposta. A documentação de uma API não é um luxo — é uma necessidade fundamental. Sem ela, desenvolvedores que consumem sua API gastarão horas em tentativa e erro, abrindo issues desnecessários e tendo uma experiência frustrante. A documentação tradicional (em arquivos Markdown, PDFs ou wikis) rapidamente fica desatualizada. Você muda um endpoint, adiciona um novo campo de validação, e a documentação permanece obsoleta. É aí que o Swagger entra em cena. O Swagger (agora chamado OpenAPI) é um padrão da indústria que permite documentar APIs de forma estruturada e automatizada. Melhor ainda: com o swaggo, você gera essa documentação diretamente a partir do seu código Go, usando anotações simples. O que é Swagger/OpenAPI e por que usar swaggo Swagger é uma especificação aberta

Entendendo APIs e a Importância da Documentação

Uma API (Application Programming Interface) é o contrato entre seu servidor e seus clientes. Quando você cria uma API em Go, está definindo endpoints, métodos HTTP, formatos de requisição e resposta. A documentação de uma API não é um luxo — é uma necessidade fundamental. Sem ela, desenvolvedores que consumem sua API gastarão horas em tentativa e erro, abrindo issues desnecessários e tendo uma experiência frustrante.

A documentação tradicional (em arquivos Markdown, PDFs ou wikis) rapidamente fica desatualizada. Você muda um endpoint, adiciona um novo campo de validação, e a documentação permanece obsoleta. É aí que o Swagger entra em cena. O Swagger (agora chamado OpenAPI) é um padrão da indústria que permite documentar APIs de forma estruturada e automatizada. Melhor ainda: com o swaggo, você gera essa documentação diretamente a partir do seu código Go, usando anotações simples.

O que é Swagger/OpenAPI e por que usar swaggo

Swagger é uma especificação aberta que descreve APIs RESTful em formato YAML ou JSON. A especificação define todos os detalhes: quais endpoints existem, que métodos HTTP aceitam, quais são os parâmetros, como são estruturados os dados de entrada e saída, códigos de status HTTP, autenticação, e muito mais. Essa especificação pode ser lida por ferramentas que geram interfaces visuais interativas, permitindo que qualquer um explore e teste sua API sem escrever código.

O swaggo é uma ferramenta Go que lê anotações (comments especiais) no seu código-fonte e gera automaticamente a especificação Swagger/OpenAPI completa. Isso significa que sua documentação vive junto com seu código. Quando você muda uma função, atualiza a anotação ali mesmo, e a documentação se regenera. Não há sincronização manual, não há risco de divergência entre código e documentação.

Instalação e Configuração Inicial

Comece instalando o swaggo via go install. Você também precisará de um roteador HTTP em Go — neste artigo usaremos Gin, que é popular e simples, mas os conceitos aplicam-se a qualquer roteador.

go install github.com/swaggo/swag/cmd/swag@latest

Em seguida, inicialize um módulo Go e adicione as dependências necessárias:

go mod init github.com/seu-usuario/sua-api
go get -u github.com/gin-gonic/gin
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files

Crie a estrutura básica do seu projeto:

seu-projeto/
├── main.go
├── handlers/
│   └── users.go
├── models/
│   └── user.go
└── go.mod

Estruturando sua API com anotações Swagger

A magia do swaggo está nas anotações. Cada endpoint, cada modelo de dados, é documentado através de comentários especiais que seguem a sintaxe do Swagger. O swaggo lê essas anotações e gera a especificação OpenAPI automaticamente.

Anotações Globais da API

Comece definindo informações globais sobre sua API no arquivo main.go:

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    _ "github.com/seu-usuario/sua-api/docs"
)

// @title           API de Gerenciamento de Usuários
// @version         1.0
// @description     Uma API simples para demonstrar Swagger com swaggo
// @termsOfService  http://swagger.io/terms/
// @contact.name    API Support
// @contact.url     http://www.swagger.io/support
// @license.name    Apache 2.0
// @license.url     http://www.apache.org/licenses/LICENSE-2.0.html
// @host            localhost:8080
// @BasePath        /api/v1
// @schemes         http https
// @securityDefinitions.apikey Bearer
// @in              header
// @name            Authorization
func main() {
    router := gin.Default()

    // Rota para acessar a documentação Swagger
    router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

    // Suas rotas de API aqui
    api := router.Group("/api/v1")
    {
        api.POST("/users", CreateUser)
        api.GET("/users/:id", GetUser)
        api.PUT("/users/:id", UpdateUser)
        api.DELETE("/users/:id", DeleteUser)
    }

    router.Run(":8080")
}

As anotações no topo do main.go definem informações globais: título da API, versão, descrição, informações de contato, segurança (no caso, um Bearer token). O import _ "github.com/seu-usuario/sua-api/docs" é importante — ele carrega o pacote docs gerado pelo swaggo.

Modelos de Dados com Tags Swagger

Defina seus modelos de dados em models/user.go:

package models

// User representa um usuário no sistema
type User struct {
    // ID único do usuário
    // example: 1
    ID int `json:"id" example:"1"`

    // Nome completo do usuário
    // example: João Silva
    Name string `json:"name" example:"João Silva"`

    // Email do usuário (deve ser único)
    // example: joao@example.com
    Email string `json:"email" example:"joao@example.com"`

    // Idade do usuário
    // example: 30
    Age int `json:"age" example:"30"`
}

// CreateUserRequest representa os dados necessários para criar um usuário
type CreateUserRequest struct {
    // Nome do usuário (obrigatório)
    // minlength: 3
    // maxlength: 100
    Name string `json:"name" binding:"required,min=3,max=100" example:"Maria Santos"`

    // Email do usuário (obrigatório)
    Email string `json:"email" binding:"required,email" example:"maria@example.com"`

    // Idade do usuário (obrigatório)
    // minimum: 18
    // maximum: 120
    Age int `json:"age" binding:"required,min=18,max=120" example:"25"`
}

Repare que não há tags especiais swagger: aqui. O swaggo é inteligente o suficiente para ler os tags JSON e os comentários acima dos campos.

Endpoints com Documentação Detalhada

Agora documente seus handlers em handlers/users.go:

package handlers

import (
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
    "github.com/seu-usuario/sua-api/models"
)

// CreateUser cria um novo usuário
// @Summary      Criar um novo usuário
// @Description  Cria um novo usuário no sistema com os dados fornecidos
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        request  body      models.CreateUserRequest  true  "Dados do usuário a ser criado"
// @Success      201      {object}  models.User               "Usuário criado com sucesso"
// @Failure      400      {object}  map[string]string         "Erro de validação"
// @Failure      500      {object}  map[string]string         "Erro interno do servidor"
// @Router       /users [post]
// @Security     Bearer
func CreateUser(c *gin.Context) {
    var req models.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user := models.User{
        ID:    1, // Em produção, isso seria um ID gerado
        Name:  req.Name,
        Email: req.Email,
        Age:   req.Age,
    }

    c.JSON(http.StatusCreated, user)
}

// GetUser obtém um usuário pelo ID
// @Summary      Obter usuário por ID
// @Description  Retorna os dados completos de um usuário específico
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        id   path      int           true  "ID do usuário"
// @Success      200  {object}  models.User   "Usuário encontrado"
// @Failure      404  {object}  map[string]string  "Usuário não encontrado"
// @Router       /users/{id} [get]
// @Security     Bearer
func GetUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
        return
    }

    // Simulação: retornar usuário
    user := models.User{
        ID:    id,
        Name:  "João Silva",
        Email: "joao@example.com",
        Age:   30,
    }

    c.JSON(http.StatusOK, user)
}

// UpdateUser atualiza um usuário existente
// @Summary      Atualizar usuário
// @Description  Atualiza os dados de um usuário existente
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        id       path      int                       true  "ID do usuário"
// @Param        request  body      models.CreateUserRequest  true  "Dados a serem atualizados"
// @Success      200      {object}  models.User               "Usuário atualizado"
// @Failure      400      {object}  map[string]string         "Erro de validação"
// @Failure      404      {object}  map[string]string         "Usuário não encontrado"
// @Router       /users/{id} [put]
// @Security     Bearer
func UpdateUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
        return
    }

    var req models.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user := models.User{
        ID:    id,
        Name:  req.Name,
        Email: req.Email,
        Age:   req.Age,
    }

    c.JSON(http.StatusOK, user)
}

// DeleteUser deleta um usuário
// @Summary      Deletar usuário
// @Description  Remove um usuário do sistema permanentemente
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        id  path      int           true  "ID do usuário"
// @Success      204 ""
// @Failure      404 {object}  map[string]string  "Usuário não encontrado"
// @Router       /users/{id} [delete]
// @Security     Bearer
func DeleteUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
        return
    }

    // Simulação: usuário deletado
    c.JSON(http.StatusNoContent, nil)
}

Cada anotação tem um propósito específico:

  • @Summary e @Description: explicam o que o endpoint faz
  • @Tags: agrupam endpoints relacionados na interface
  • @Param: documentam parâmetros (path, query, body)
  • @Success e @Failure: documentam respostas possíveis com seus status codes e estruturas
  • @Router: define o caminho e método HTTP
  • @Security: indica que o endpoint requer autenticação

Gerando a Documentação e Acessando a Interface

Após anotar seu código, execute o swaggo para gerar a especificação OpenAPI:

swag init

Esse comando procura por anotações em seu projeto (começando por main.go por padrão) e cria um diretório docs/ com arquivos YAML e JSON que descrevem sua API. Você verá uma mensagem de sucesso indicando que o arquivo swagger.yaml foi gerado.

Agora execute sua aplicação:

go run main.go

Acesse http://localhost:8080/swagger/index.html em seu navegador. Você verá a interface Swagger UI — uma página interativa e bonita onde pode explorar todos os seus endpoints, ver seus parâmetros, testar requisições diretamente da interface, e visualizar respostas reais.

Regenerando Documentação após Alterações

Toda vez que você alterar anotações ou adicionar novos endpoints, execute swag init novamente. Para desenvolvimento contínuo, você pode instalar uma ferramenta como air para recompilar automaticamente:

go install github.com/cosmtrek/air@latest
air

Isso executará swag init a cada mudança nos arquivos, mantendo a documentação sempre sincronizada com seu código.

Boas Práticas e Padrões Avançados

Versionamento de API

É uma prática saudável versionamento de API. A rota /api/v1 em nosso exemplo já segue esse padrão. Se você precisar fazer mudanças incompatíveis no futuro, pode manter /api/v1 para clientes antigos e criar /api/v2 com as novas mudanças, sem quebrar ninguém.

Documentação de Respostas de Erro

Sempre documente explicitamente quais erros um endpoint pode retornar. Isso ajuda consumidores a lidar com falhas corretamente:

// @Failure      400  {object}  ErrorResponse  "Erro de validação"
// @Failure      401  {object}  ErrorResponse  "Não autenticado"
// @Failure      403  {object}  ErrorResponse  "Sem permissão"
// @Failure      500  {object}  ErrorResponse  "Erro interno do servidor"

Defina um tipo ErrorResponse claro em seus modelos:

type ErrorResponse struct {
    Code    int    `json:"code" example:"400"`
    Message string `json:"message" example:"Email já existe"`
    Details string `json:"details,omitempty" example:"O campo 'email' deve ser único"`
}

Documentando Query Parameters e Headers

Se seu endpoint aceita filtros ou parâmetros opcionais:

// @Param  limit  query  int  false  "Número máximo de resultados"  default(10)
// @Param  offset query  int  false  "Número de registros a pular"  default(0)
// @Param  sort   query  string false "Campo para ordenação"  Enums(id,name,email)

Para headers customizados:

// @Param  X-Request-ID  header  string  false  "ID único da requisição"

Autenticação e Segurança

Já mencionamos @Security Bearer nas anotações. Você pode definir múltiplos esquemas de segurança globalmente e aplicar a endpoints específicos:

// @securityDefinitions.apikey ApiKeyAuth
// @in                          header
// @name                         X-API-Key

// @securityDefinitions.basic   BasicAuth

Depois, em cada endpoint, especifique qual segurança se aplica:

// @Security ApiKeyAuth
// @Security BasicAuth

Conclusão

A documentação de APIs é tão importante quanto o código que as implementa. O Swagger e swaggo resolvem o problema clássico de documentação desatualizada, permitindo que você mantenha a especificação viva no seu código-fonte. Primeiro aprendizado: anotações no código são manutenção zero de documentação — você muda o código, atualiza a anotação ali mesmo, executa swag init, e pronto.

Segundo ponto-chave: a interface Swagger UI transforma especificação técnica em experiência visual. Desenvolvedores podem explorar sua API interativamente, testar endpoints sem escrever curl, entender estruturas de dados rapidamente. Isso reduz drasticamente o tempo de onboarding e diminui bugs de integração causados por desentendimentos sobre a API.

Por fim, swaggo não adiciona overhead ao seu projeto — trata-se apenas de geração de arquivos estáticos (YAML/JSON), não de middleware ou complexidade em runtime. A documentação é gerada uma única vez durante desenvolvimento e pode ser servida estaticamente em produção, sem custo de performance.

Referências


Artigos relacionados