Aggiungere Swagger al tuo Go API
Generare automaticamente i documenti OpenAPI dagli annotazioni del codice
La documentazione API è fondamentale per qualsiasi applicazione moderna, e per Go APIs Swagger (OpenAPI) è diventato lo standard dell’industria. Per gli sviluppatori Go, swaggo fornisce una soluzione elegante per generare una documentazione API completa direttamente dalle annotazioni del codice.
Questa bella immagine è generata da AI model Flux 1 dev.
Perché Swagger è importante per le API Go
Quando si costruiscono API REST, la documentazione spesso diventa obsoleta man mano che il codice evolve. Swagger risolve questo problema generando la documentazione dal codice sorgente, assicurando che rimanga sincronizzata con l’implementazione. L’interfaccia utente interattiva di Swagger permette agli sviluppatori di testare gli endpoint direttamente dal browser, migliorando significativamente l’esperienza dello sviluppatore.
Per i team che costruiscono microservizi o API pubbliche, la documentazione Swagger diventa essenziale per:
- Generazione del client: Creare automaticamente librerie client in diversi linguaggi
- Test del contratto: Validare le richieste e le risposte rispetto agli schemi definiti
- Collaborazione tra team: Fornire un’unica fonte di verità per i contratti API
- Onboarding degli sviluppatori: I nuovi membri del team possono esplorare le API in modo interattivo
Inizio con swaggo
La libreria swaggo è lo strumento più popolare per aggiungere il supporto Swagger alle applicazioni Go. Funziona analizzando commenti speciali nel codice e generando file di specifica OpenAPI 3.0.
Installazione
Per prima cosa, installa lo strumento CLI swag:
go install github.com/swaggo/swag/cmd/swag@latest
Poi aggiungi il pacchetto middleware Swagger appropriato per il tuo framework. Per Gin:
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
Per Echo:
go get -u github.com/swaggo/echo-swagger
Per Fiber:
go get -u github.com/gofiber/swagger
Configurazione base
Inizia aggiungendo informazioni generali sull’API nel file main.go. Simile a come strutturerebbero un REST API in Go, le annotazioni devono essere chiare e descrittive:
// @title Product API
// @version 1.0
// @description Un'API di gestione dei prodotti con documentazione Swagger
// @termsOfService http://swagger.io/terms/
// @contact.name Supporto 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" seguito da uno spazio e un token JWT.
func main() {
// Il codice dell'applicazione
}
Implementazione con il framework Gin
Implementiamo un esempio completo utilizzando Gin. Per prima cosa, definiamo i modelli di dati con tag struct:
type Product struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"Laptop" binding:"required"`
Description string `json:"description" example:"Laptop ad alte prestazioni"`
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:"Input non valido"`
Message string `json:"message" example:"Il nome del prodotto è richiesto"`
}
Ora annotiamo le funzioni gestore. Quando si lavora con operazioni del database, queste annotazioni aiutano a documentare il flusso dei dati:
// GetProduct godoc
// @Summary Ottenere un prodotto per ID
// @Description Recuperare un singolo prodotto tramite il suo identificatore unico
// @Tags prodotti
// @Accept json
// @Produce json
// @Param id path int true "ID del prodotto"
// @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")
// Implementazione qui
c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}
// CreateProduct godoc
// @Summary Creare un nuovo prodotto
// @Description Aggiungere un nuovo prodotto al catalogo
// @Tags prodotti
// @Accept json
// @Produce json
// @Param product body Product true "Oggetto prodotto"
// @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: "Richiesta non valida", Message: err.Error()})
return
}
// Salvare nel database
c.JSON(201, product)
}
Generazione della documentazione
Dopo aver annotato il codice, genera la documentazione Swagger:
swag init
Questo crea una cartella docs con swagger.json, swagger.yaml e file Go. Importa e registra l’endpoint Swagger:
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "yourproject/docs" // Importa le documentazioni generate
)
func main() {
r := gin.Default()
// Route API
v1 := r.Group("/api/v1")
{
v1.GET("/products/:id", GetProduct)
v1.POST("/products", CreateProduct)
}
// Endpoint Swagger
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
Ora accedi alla tua documentazione API interattiva a http://localhost:8080/swagger/index.html.
Implementazione con il framework Echo
Gli utenti Echo seguono un modello simile ma con middleware specifici per Echo:
package main
import (
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger"
_ "yourproject/docs"
)
func main() {
e := echo.New()
// Route API
api := e.Group("/api/v1")
api.GET("/products/:id", getProduct)
api.POST("/products", createProduct)
// Endpoint Swagger
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.Start(":8080")
}
Implementazione con il framework Fiber
L’implementazione di Fiber è altrettanto semplice:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
_ "yourproject/docs"
)
func main() {
app := fiber.New()
// Route API
api := app.Group("/api/v1")
api.Get("/products/:id", getProduct)
api.Post("/products", createProduct)
// Endpoint Swagger
app.Get("/swagger/*", swagger.HandlerDefault)
app.Listen(":8080")
}
Annotazioni Swagger avanzate
Documentare corpi di richiesta complessi
Per strutture annidate o array:
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 Creare un nuovo ordine
// @Description Creare un ordine con diversi articoli e informazioni di spedizione
// @Tags ordini
// @Accept json
// @Produce json
// @Param order body CreateOrderRequest true "Dettagli dell'ordine"
// @Success 201 {object} Order
// @Failure 400 {object} ErrorResponse
// @Failure 422 {object} ErrorResponse
// @Security Bearer
// @Router /orders [post]
func CreateOrder(c *gin.Context) {
// Implementazione
}
Documentare il caricamento di file
// UploadImage godoc
// @Summary Caricare un'immagine del prodotto
// @Description Caricare un file immagine per un prodotto
// @Tags prodotti
// @Accept multipart/form-data
// @Produce json
// @Param id path int true "ID del prodotto"
// @Param file formData file true "File immagine"
// @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")
// Gestione del caricamento
}
Parametri di query e paginazione
// ListProducts godoc
// @Summary Elenco dei prodotti con paginazione
// @Description Ottenere un elenco paginato dei prodotti con filtraggio opzionale
// @Tags prodotti
// @Accept json
// @Produce json
// @Param page query int false "Numero di pagina" default(1)
// @Param page_size query int false "Elementi per pagina" default(10)
// @Param category query string false "Filtrare per categoria"
// @Param min_price query number false "Prezzo minimo"
// @Param max_price query number false "Prezzo massimo"
// @Success 200 {array} Product
// @Failure 400 {object} ErrorResponse
// @Router /products [get]
func ListProducts(c *gin.Context) {
// Implementazione con paginazione
}
Autenticazione e sicurezza
Documenta diversi metodi di autenticazione nell’API. Per applicazioni multi-tenant, una corretta documentazione dell’autenticazione è cruciale:
Autenticazione con token Bearer
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Tipo "Bearer" seguito da uno spazio e un token JWT.
Autenticazione con chiave API
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description Chiave API per l'autenticazione
Autenticazione OAuth2
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
Autenticazione Basic
// @securityDefinitions.basic BasicAuth
Applica la sicurezza a endpoint specifici:
// @Security Bearer
// @Security ApiKeyAuth
Personalizzazione dell’interfaccia Swagger UI
Puoi personalizzare l’aspetto e il comportamento dell’interfaccia Swagger UI:
// Configurazione personalizzata
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
// Con titolo personalizzato
r.GET("/swagger/*any", ginSwagger.WrapHandler(
swaggerFiles.Handler,
ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
ginSwagger.DefaultModelsExpandDepth(-1),
))
Per disattivare Swagger in produzione:
if os.Getenv("ENV") != "production" {
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
Integrazione con CI/CD
Automatizza la generazione della documentazione Swagger nel tuo pipeline CI/CD:
# Esempio GitHub Actions
name: Genera documentazione Swagger
on: [push]
jobs:
swagger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configura Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Installa swag
run: go install github.com/swaggo/swag/cmd/swag@latest
- name: Genera documentazione Swagger
run: swag init
- name: Commita le documentazioni
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add docs/
git commit -m "Aggiorna la documentazione Swagger" || exit 0
git push
Best Practices
1. Stile coerente delle annotazioni
Mantieni un formato coerente su tutti gli endpoint:
// HandlerName godoc
// @Summary Breve descrizione (sotto i 50 caratteri)
// @Description Descrizione dettagliata di ciò che fa l'endpoint
// @Tags nome-risorsa
// @Accept json
// @Produce json
// @Param nome posizione tipo richiesto "descrizione"
// @Success 200 {object} TipoRisposta
// @Failure 400 {object} ErrorResponse
// @Router /path [metodo]
2. Usa esempi descrittivi
Aggiungi esempi realistici per aiutare i consumatori dell’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. Documenta tutti i codici di risposta
Includi tutti i possibili codici di stato HTTP:
// @Success 200 {object} Product
// @Success 201 {object} Product
// @Failure 400 {object} ErrorResponse "Richiesta non valida"
// @Failure 401 {object} ErrorResponse "Non autorizzato"
// @Failure 403 {object} ErrorResponse "Vietato"
// @Failure 404 {object} ErrorResponse "Non trovato"
// @Failure 422 {object} ErrorResponse "Errore di validazione"
// @Failure 500 {object} ErrorResponse "Errore interno del server"
4. Versiona l’API
Utilizza il percorso base corretto per la versione:
// @BasePath /api/v1
E organizza il tuo codice di conseguenza:
v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")
5. Raggruppa gli endpoint correlati
Usa i tag per organizzare logicamente gli endpoint:
// @Tags prodotti
// @Tags ordini
// @Tags utenti
6. Mantieni la documentazione aggiornata
Esegui swag init prima di ogni commit o integralo nel tuo processo di costruzione:
#!/bin/bash
# hook pre-commit
swag init
git add docs/
Test della documentazione Swagger
Quando si lavora con architetture serverless come AWS Lambda, il test della documentazione API diventa ancora più importante:
func TestSwaggerGeneration(t *testing.T) {
// Verifica che swagger.json esista
_, err := os.Stat("./docs/swagger.json")
if err != nil {
t.Fatal("swagger.json non trovato, esegui 'swag init'")
}
// Verifica che l'endpoint swagger risponda
r := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
Problemi comuni e soluzioni
Documentazione non aggiornata
Se i cambiamenti non appaiono, assicurati di rigenerare le documentazioni:
swag init --parseDependency --parseInternal
La flag --parseDependency analizza le dipendenze esterne, e --parseInternal analizza i pacchetti interni.
Tipi personalizzati non riconosciuti
Per i tipi da pacchetti esterni, usa il tag swaggertype:
type CustomTime struct {
time.Time
}
func (CustomTime) SwaggerDoc() map[string]string {
return map[string]string{
"time": "Timestamp RFC3339",
}
}
Oppure usa il tag swaggertype:
type Product struct {
ID int `json:"id"`
UpdatedAt CustomTime `json:"updated_at" swaggertype:"string" format:"date-time"`
}
Array e enum
Documenta i tipi di array e gli enum:
type Filter struct {
Status []string `json:"status" enums:"attivo,non attivo,in sospeso"`
Tags []string `json:"tags"`
}
// @Param status query string false "Filtro di stato" Enums(attivo, non attivo, in sospeso)
Approcci alternativi
Mentre swaggo è la scelta più popolare, esistono altre opzioni:
go-swagger
Un’alternativa più ricca di funzionalità ma complessa:
brew install go-swagger
swagger generate spec -o ./swagger.json
File OpenAPI manuali
Per il completo controllo, scrivi specifiche OpenAPI manualmente in YAML:
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: Elenco prodotti
responses:
'200':
description: Successo
Poi servilo con:
r.StaticFile("/openapi.yaml", "./openapi.yaml")
Integrazione con AI e LLM
Quando si costruiscono API che si integrano con servizi AI, una corretta documentazione diventa cruciale. Per esempio, quando si lavora con output strutturati degli LLM, Swagger aiuta a documentare gli schemi di richiesta e risposta complessi:
type LLMRequest struct {
Prompt string `json:"prompt" example:"Riassumi questo testo"`
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 Genera output strutturato degli LLM
// @Description Genera testo con schema di output vincolato
// @Tags llm
// @Accept json
// @Produce json
// @Param request body LLMRequest true "Parametri LLM"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorResponse
// @Router /llm/generate [post]
func GenerateStructured(c *gin.Context) {
// Implementazione
}
Considerazioni sulle prestazioni
La documentazione Swagger ha un impatto minimo sulle prestazioni:
- Tempo di costruzione:
swag initrichiede 1-3 secondi per la maggior parte dei progetti - Runtime: La documentazione viene caricata una volta all’avvio
- Memoria: Aggiunge in genere 1-2MB alla dimensione del binario
- Tempo di risposta: Non influisce sugli endpoint API stessi
Per API molto grandi (100+ endpoint), considera:
- Suddividere in diversi file Swagger
- Caricamento ritardato degli asset di Swagger UI
- Servire la documentazione da un servizio separato
Considerazioni sulla sicurezza
Quando si espone la documentazione Swagger:
- Disattivala in produzione (se l’API è interna):
if os.Getenv("ENV") == "production" {
// Non registrare l'endpoint Swagger
return
}
- Aggiungi l’autenticazione:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- Limita la frequenza dell’endpoint:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- Non esporre mai dettagli interni:
- Non documentare endpoint interni
- Evitare di esporre direttamente gli schemi del database
- Sanificare i messaggi di errore nella documentazione
Conclusione
Aggiungere la documentazione Swagger al tuo API Go trasforma l’esperienza dello sviluppatore da lavoro a caso a esplorazione guidata. La libreria swaggo rende questo processo semplice generando una completa documentazione OpenAPI direttamente dalle tue annotazioni di codice.
Punti chiave:
- Inizia con annotazioni di base e espandile gradualmente
- Mantieni la documentazione sincronizzata con il codice attraverso CI/CD
- Usa l’interfaccia utente Swagger per il test interattivo durante lo sviluppo
- Documenta l’autenticazione, gli errori e i casi limite in modo completo
- Considera le implicazioni di sicurezza quando si espone la documentazione
Che tu stia costruendo microservizi, API pubbliche o strumenti interni, la documentazione Swagger paga i suoi frutti in un ridotto carico di supporto, un più rapido onboarding e una migliore progettazione API. L’investimento iniziale per imparare la sintassi delle annotazioni diventa rapidamente routine, e la generazione automatica assicura che la documentazione non rimanga mai indietro rispetto all’implementazione.
Per gli sviluppatori Go, la combinazione di tipizzazione forte, generazione di codice e sistema di annotazioni di swaggo crea un potente workflow che rende la documentazione API una parte naturale del processo di sviluppo, piuttosto che un’opzione posticipata.
Link utili
- Go Cheatsheet
- Implementing REST APIs in Go
- Comparing Go ORMs for PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Multi-Tenancy Database Patterns with examples in Go
- LLMs with Structured Output: Ollama, Qwen3 & Python or Go
- AWS lambda performance: JavaScript vs Python vs Golang