Añadir Swagger a tu API en Go
Generar automáticamente documentos OpenAPI a partir de anotaciones en el código
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.
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 inittoma 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:
- Deshabilite en producción (si la API es interna):
if os.Getenv("ENV") == "production" {
// No registrar punto final de Swagger
return
}
- Añadir autenticación:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- Limitar la tasa del punto final:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- 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
- Hoja de trucos de Go
- Construyendo APIs REST en Go
- Comparando ORMs de Go para PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Patrones de base de datos multiinquilino con ejemplos en Go
- LLMs con salida estructurada: Ollama, Qwen3 y Python o Go
- Rendimiento de AWS lambda: JavaScript vs Python vs Golang