Añadir Swagger a tu API en Go

Generar automáticamente documentos OpenAPI a partir de anotaciones en el código

Índice

La documentación de API es crucial para cualquier aplicación moderna, y para Go APIs Swagger (OpenAPI) ha become el estándar de la industria. Para los desarrolladores de Go, swaggo proporciona una solución elegante para generar documentación de API completa directamente desde las anotaciones del código.

swagger api specs on agile board Esta imagen agradable se genera mediante AI model Flux 1 dev.

Por qué Swagger importa para APIs de Go

Cuando se construyen APIs REST, la documentación a menudo se vuelve obsoleta a medida que evoluciona el código. Swagger resuelve esto generando documentación desde su código fuente, asegurando que permanezca sincronizada con su implementación. La interfaz interactiva de Swagger UI permite a los desarrolladores probar los puntos finales directamente desde el navegador, mejorando significativamente la experiencia del desarrollador.

Para equipos que construyen microservicios o APIs públicas, la documentación de Swagger se vuelve esencial para:

  • Generación de cliente: Crear automáticamente bibliotecas de cliente en varios idiomas
  • Pruebas de contrato: Validar solicitudes y respuestas contra esquemas definidos
  • Colaboración de equipo: Proporcionar una única fuente de verdad para contratos de API
  • Onboarding de desarrolladores: Los nuevos miembros del equipo pueden explorar APIs de forma interactiva

Comenzando con swaggo

La biblioteca swaggo es la herramienta más popular para agregar soporte de Swagger a aplicaciones de Go. Funciona analizando comentarios especiales en su código y generando archivos de especificación OpenAPI 3.0.

Instalación

Primero, instale la herramienta CLI de swag:

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

Luego agregue el paquete de middleware de Swagger adecuado para su framework. Para Gin:

go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files

Para Echo:

go get -u github.com/swaggo/echo-swagger

Para Fiber:

go get -u github.com/gofiber/swagger

Configuración básica

Comience agregando información general de API en su archivo main.go. Similar a cómo estructuraría una API REST en Go, las anotaciones deben ser claras y descriptivas:

// @title           Product API
// @version         1.0
// @description     Una API de gestión de productos con documentación de Swagger
// @termsOfService  http://swagger.io/terms/

// @contact.name   Soporte de API
// @contact.url    http://www.swagger.io/support
// @contact.email  support@swagger.io

// @license.name  Apache 2.0
// @license.url   http://www.apache.org/licenses/LICENSE-2.0.html

// @host      localhost:8080
// @BasePath  /api/v1

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Tipo "Bearer" seguido de un espacio y un token JWT.

func main() {
    // Su código de aplicación
}

Implementación con el framework Gin

Vamos a implementar un ejemplo completo usando Gin. Primero, defina sus modelos de datos con etiquetas de struct:

type Product struct {
    ID          int     `json:"id" example:"1"`
    Name        string  `json:"name" example:"Laptop" binding:"required"`
    Description string  `json:"description" example:"Laptop de alto rendimiento"`
    Price       float64 `json:"price" example:"999.99" binding:"required,gt=0"`
    Stock       int     `json:"stock" example:"50"`
}

type ErrorResponse struct {
    Error   string `json:"error" example:"Entrada inválida"`
    Message string `json:"message" example:"El nombre del producto es obligatorio"`
}

Ahora anote sus funciones de controlador. Cuando se trabaja con operaciones de base de datos, estas anotaciones ayudan a documentar el flujo de datos:

// GetProduct godoc
// @Summary      Obtener producto por ID
// @Description  Recuperar un producto único por su identificador único
// @Tags         productos
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "ID del producto"
// @Success      200  {object}  Product
// @Failure      400  {object}  ErrorResponse
// @Failure      404  {object}  ErrorResponse
// @Router       /products/{id} [get]
func GetProduct(c *gin.Context) {
    id := c.Param("id")
    // Implementación aquí
    c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

// CreateProduct godoc
// @Summary      Crear un nuevo producto
// @Description  Añadir un nuevo producto al catálogo
// @Tags         productos
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "Objeto de producto"
// @Success      201      {object}  Product
// @Failure      400      {object}  ErrorResponse
// @Security     Bearer
// @Router       /products [post]
func CreateProduct(c *gin.Context) {
    var product Product
    if err := c.ShouldBindJSON(&product); err != nil {
        c.JSON(400, ErrorResponse{Error: "Solicitud incorrecta", Message: err.Error()})
        return
    }
    // Guardar en la base de datos
    c.JSON(201, product)
}

Generando documentación

Después de anotar su código, genere la documentación de Swagger:

swag init

Esto crea una carpeta docs con swagger.json, swagger.yaml y archivos Go. Importe y registre el punto final de Swagger:

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    
    _ "yourproject/docs" // Importar documentos generados
)

func main() {
    r := gin.Default()
    
    // Rutas de API
    v1 := r.Group("/api/v1")
    {
        v1.GET("/products/:id", GetProduct)
        v1.POST("/products", CreateProduct)
    }
    
    // Punto final de Swagger
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    
    r.Run(":8080")
}

Ahora acceda a su documentación de API interactiva en http://localhost:8080/swagger/index.html.

Implementación con el framework Echo

Los usuarios de Echo siguen un patrón similar pero con middleware específico de Echo:

package main

import (
    "github.com/labstack/echo/v4"
    echoSwagger "github.com/swaggo/echo-swagger"
    
    _ "yourproject/docs"
)

func main() {
    e := echo.New()
    
    // Rutas de API
    api := e.Group("/api/v1")
    api.GET("/products/:id", getProduct)
    api.POST("/products", createProduct)
    
    // Punto final de Swagger
    e.GET("/swagger/*", echoSwagger.WrapHandler)
    
    e.Start(":8080")
}

Implementación con el framework Fiber

La implementación de Fiber es igual de sencilla:

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/swagger"
    
    _ "yourproject/docs"
)

func main() {
    app := fiber.New()
    
    // Rutas de API
    api := app.Group("/api/v1")
    api.Get("/products/:id", getProduct)
    api.Post("/products", createProduct)
    
    // Punto final de Swagger
    app.Get("/swagger/*", swagger.HandlerDefault)
    
    app.Listen(":8080")
}

Anotaciones avanzadas de Swagger

Documentando cuerpos de solicitud complejos

Para estructuras anidadas o matrices:

type CreateOrderRequest struct {
    CustomerID int           `json:"customer_id" example:"123" binding:"required"`
    Items      []OrderItem   `json:"items" binding:"required,min=1"`
    ShippingAddress Address  `json:"shipping_address" binding:"required"`
}

type OrderItem struct {
    ProductID int `json:"product_id" example:"1" binding:"required"`
    Quantity  int `json:"quantity" example:"2" binding:"required,min=1"`
}

type Address struct {
    Street  string `json:"street" example:"123 Main St" binding:"required"`
    City    string `json:"city" example:"New York" binding:"required"`
    ZipCode string `json:"zip_code" example:"10001" binding:"required"`
}

// CreateOrder godoc
// @Summary      Crear un nuevo pedido
// @Description  Crear un pedido con múltiples artículos e información de envío
// @Tags         pedidos
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "Detalles del pedido"
// @Success      201    {object}  Order
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // Implementación
}

Documentando subidas de archivos

// UploadImage godoc
// @Summary      Subir imagen de producto
// @Description  Subir un archivo de imagen para un producto
// @Tags         productos
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "ID del producto"
// @Param        file  formData  file  true  "Archivo de imagen"
// @Success      200   {object}  map[string]string
// @Failure      400   {object}  ErrorResponse
// @Security     Bearer
// @Router       /products/{id}/image [post]
func UploadImage(c *gin.Context) {
    file, _ := c.FormFile("file")
    // Manejar subida
}

Parámetros de consulta y paginación

// ListProducts godoc
// @Summary      Listar productos con paginación
// @Description  Obtener lista paginada de productos con filtrado opcional
// @Tags         productos
// @Accept       json
// @Produce      json
// @Param        page      query     int     false  "Número de página" default(1)
// @Param        page_size query     int     false  "Elementos por página" default(10)
// @Param        category  query     string  false  "Filtrar por categoría"
// @Param        min_price query     number  false  "Precio mínimo"
// @Param        max_price query     number  false  "Precio máximo"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // Implementación con paginación
}

Autenticación y seguridad

Documente diferentes métodos de autenticación en su API. Para aplicaciones multiinquilino, la documentación adecuada de autenticación es crucial:

Autenticación con token Bearer

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Tipo "Bearer" seguido de un espacio y un token JWT.

Autenticación con API Key

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description Clave API para autenticación

Autenticación OAuth2

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Concede acceso de escritura
// @scope.admin Concede acceso de lectura y escritura a información administrativa

Autenticación básica

// @securityDefinitions.basic BasicAuth

Aplicar seguridad a puntos finales específicos:

// @Security Bearer
// @Security ApiKeyAuth

Personalizando la interfaz de Swagger UI

Puede personalizar la apariencia y el comportamiento de la interfaz de Swagger UI:

// Configuración personalizada
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))

// Con título personalizado
r.GET("/swagger/*any", ginSwagger.WrapHandler(
    swaggerFiles.Handler,
    ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
    ginSwagger.DefaultModelsExpandDepth(-1),
))

Para deshabilitar Swagger en producción:

if os.Getenv("ENV") != "production" {
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

Integración con CI/CD

Automatice la generación de documentación de Swagger en su pipeline de CI/CD:

# Ejemplo de GitHub Actions
name: Generar documentación de Swagger
on: [push]

jobs:
  swagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Configurar Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Instalar swag
        run: go install github.com/swaggo/swag/cmd/swag@latest
      
      - name: Generar documentación de Swagger
        run: swag init
      
      - name: Commitar documentos
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add docs/
          git commit -m "Actualizar documentación de Swagger" || exit 0
          git push          

Mejores prácticas

1. Estilo de anotación consistente

Mantenga un formato consistente en todos los puntos finales:

// HandlerName godoc
// @Summary      Descripción breve (menos de 50 caracteres)
// @Description  Descripción detallada de lo que hace el punto final
// @Tags         nombre-del-recurso
// @Accept       json
// @Produce      json
// @Param        nombre  ubicación  tipo  requerido  "descripción"
// @Success      200   {object}  TipoDeRespuesta
// @Failure      400   {object}  ErrorResponse
// @Router       /ruta [método]

2. Usar ejemplos descriptivos

Añada ejemplos realistas para ayudar a los consumidores de la API:

type User struct {
    ID        int       `json:"id" example:"1"`
    Email     string    `json:"email" example:"user@example.com"`
    CreatedAt time.Time `json:"created_at" example:"2025-01-15T10:30:00Z"`
}

3. Documentar todos los códigos de respuesta

Incluya todos los códigos de estado HTTP posibles:

// @Success      200  {object}  Product
// @Success      201  {object}  Product
// @Failure      400  {object}  ErrorResponse "Solicitud incorrecta"
// @Failure      401  {object}  ErrorResponse "No autorizado"
// @Failure      403  {object}  ErrorResponse "Prohibido"
// @Failure      404  {object}  ErrorResponse "No encontrado"
// @Failure      422  {object}  ErrorResponse "Error de validación"
// @Failure      500  {object}  ErrorResponse "Error interno del servidor"

4. Versionar su API

Use la versión adecuada en la ruta base:

// @BasePath  /api/v1

Y organice su código en consecuencia:

v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")

5. Agrupar puntos finales relacionados

Use etiquetas para organizar lógicamente los puntos finales:

// @Tags productos
// @Tags pedidos
// @Tags usuarios

6. Mantener la documentación actualizada

Ejecute swag init antes de cada commit o intégralo en su proceso de construcción:

#!/bin/bash
# hook de pre-commit
swag init
git add docs/

Pruebas de documentación de Swagger

Cuando se trabaja con arquitecturas serverless como AWS Lambda, probar su documentación de API se vuelve aún más importante:

func TestSwaggerGeneration(t *testing.T) {
    // Verificar si swagger.json existe
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("swagger.json no encontrado, ejecutar 'swag init'")
    }
    
    // Verificar si el punto final de swagger responde
    r := setupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

Problemas comunes y soluciones

Documentación que no se actualiza

Si los cambios no aparecen, asegúrese de regenerar las documentaciones:

swag init --parseDependency --parseInternal

La bandera --parseDependency analiza dependencias externas, y --parseInternal analiza paquetes internos.

Tipos personalizados no reconocidos

Para tipos de paquetes externos, use la etiqueta swaggertype:

type CustomTime struct {
    time.Time
}

func (CustomTime) SwaggerDoc() map[string]string {
    return map[string]string{
        "time": "Marca de tiempo RFC3339",
    }
}

O use la etiqueta swaggertype:

type Product struct {
    ID        int        `json:"id"`
    UpdatedAt CustomTime `json:"updated_at" swaggertype:"string" format:"date-time"`
}

Matrices y enumeraciones

Documente tipos de matriz y enumeraciones:

type Filter struct {
    Status []string `json:"status" enums:"active,inactive,pending"`
    Tags   []string `json:"tags"`
}

// @Param  status  query  string  false  "Filtro de estado" Enums(active, inactive, pending)

Enfoques alternativos

Aunque swaggo es la opción más popular, existen otras alternativas:

go-swagger

Una alternativa más rica en características pero compleja:

brew install go-swagger
swagger generate spec -o ./swagger.json

Archivos OpenAPI manuales

Para un control total, escriba especificaciones OpenAPI manualmente en YAML:

openapi: 3.0.0
info:
  title: API de productos
  version: 1.0.0
paths:
  /products:
    get:
      summary: Listar productos
      responses:
        '200':
          description: Éxito

Luego sirva con:

r.StaticFile("/openapi.yaml", "./openapi.yaml")

Integración con IA y LLMs

Cuando se construyen APIs que se integran con servicios de IA, la documentación adecuada se vuelve crucial. Por ejemplo, cuando se trabaja con salidas estructuradas de LLM, Swagger ayuda a documentar esquemas complejos de solicitud y respuesta:

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"Resumir este texto"`
    Model       string            `json:"model" example:"qwen2.5:latest"`
    Temperature float64           `json:"temperature" example:"0.7" minimum:"0" maximum:"2"`
    MaxTokens   int               `json:"max_tokens" example:"1000" minimum:"1"`
    Schema      map[string]interface{} `json:"schema,omitempty"`
}

// GenerateStructured godoc
// @Summary      Generar salida estructurada de LLM
// @Description  Generar texto con esquema de salida restringido
// @Tags         llm
// @Accept       json
// @Produce      json
// @Param        request  body      LLMRequest  true  "Parámetros de LLM"
// @Success      200      {object}  map[string]interface{}
// @Failure      400      {object}  ErrorResponse
// @Router       /llm/generate [post]
func GenerateStructured(c *gin.Context) {
    // Implementación
}

Consideraciones de rendimiento

La documentación de Swagger tiene un impacto mínimo en el rendimiento:

  • Tiempo de construcción: swag init toma 1-3 segundos para la mayoría de los proyectos
  • Tiempo de ejecución: La documentación se carga una vez al inicio
  • Memoria: Normalmente agrega 1-2 MB al tamaño del binario
  • Tiempo de respuesta: No afecta a los puntos finales de la API en sí mismos

Para APIs muy grandes (más de 100 puntos finales), considere:

  • Dividir en múltiples archivos de Swagger
  • Cargar de forma perezosa los activos de Swagger UI
  • Servir la documentación desde un servicio separado

Consideraciones de seguridad

Cuando se expone la documentación de Swagger:

  1. Deshabilite en producción (si la API es interna):
if os.Getenv("ENV") == "production" {
    // No registrar punto final de Swagger
    return
}
  1. Añadir autenticación:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Limitar la tasa del punto final:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Nunca exponga detalles internos:
  • No documente puntos finales internos
  • Evite exponer esquemas de base de datos directamente
  • Sanitice mensajes de error en la documentación

Conclusión

Añadir documentación de Swagger a su API de Go transforma la experiencia del desarrollador de suposiciones a exploración guiada. La biblioteca swaggo hace este proceso sencillo al generar documentación OpenAPI completa desde sus anotaciones de código.

Puntos clave:

  • Comience con anotaciones básicas y amplíelas gradualmente
  • Mantenga la documentación sincronizada con el código mediante CI/CD
  • Use la interfaz de Swagger UI para pruebas interactivas durante el desarrollo
  • Documente autenticación, errores y casos límite de forma exhaustiva
  • Considere las implicaciones de seguridad al exponer la documentación

Ya sea que esté construyendo microservicios, APIs públicas o herramientas internas, la documentación de Swagger paga dividendos en menor carga de soporte, más rápido onboarding y mejor diseño de API. La inversión inicial en aprender la sintaxis de anotación rápidamente se convierte en rutina, y la generación automática asegura que su documentación nunca se quede atrás de su implementación.

Para desarrolladores de Go, la combinación de tipado fuerte, generación de código y el sistema de anotaciones de swaggo crea un flujo de trabajo poderoso que hace que la documentación de API sea una parte natural del proceso de desarrollo en lugar de un pensamiento posterior.

Enlaces útiles

Recursos externos