Aggiungere Swagger al tuo Go API

Generare automaticamente i documenti OpenAPI dagli annotazioni del codice

Indice

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.

swagger api specs on agile board 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 init richiede 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:

  1. Disattivala in produzione (se l’API è interna):
if os.Getenv("ENV") == "production" {
    // Non registrare l'endpoint Swagger
    return
}
  1. Aggiungi l’autenticazione:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Limita la frequenza dell’endpoint:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. 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.

Risorse esterne