Ajout de Swagger à votre API Go

Générer automatiquement des documents OpenAPI à partir des annotations du code

Sommaire

La documentation API est cruciale pour toute application moderne, et pour Go APIs Swagger (OpenAPI) est devenue la norme industrielle. Pour les développeurs Go, swaggo propose une solution élégante pour générer une documentation API complète directement à partir des annotations du code.

swagger api specs on agile board Cette belle image est générée par AI model Flux 1 dev.

Pourquoi Swagger est important pour les APIs Go

Lors de la création d’API REST, la documentation devient souvent obsolète à mesure que le code évolue. Swagger résout ce problème en générant la documentation à partir de votre code source, assurant ainsi qu’elle reste synchronisée avec votre implémentation. L’interface interactive Swagger UI permet aux développeurs de tester les points de terminaison directement depuis le navigateur, améliorant considérablement l’expérience du développeur.

Pour les équipes qui construisent des microservices ou des API publiques, la documentation Swagger devient essentielle pour :

  • Génération du client : Créer automatiquement des bibliothèques client dans plusieurs langages
  • Test des contrats : Valider les requêtes et les réponses contre des schémas définis
  • Collaboration d’équipe : Fournir une seule source de vérité pour les contrats API
  • Formation des nouveaux membres d’équipe : Les nouveaux membres peuvent explorer les API de manière interactive

Démarrage avec swaggo

La bibliothèque swaggo est l’outil le plus populaire pour ajouter un support Swagger aux applications Go. Elle fonctionne en analysant des commentaires spéciaux dans votre code et en générant des fichiers de spécification OpenAPI 3.0.

Installation

Installez d’abord l’outil CLI swag :

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

Ensuite, ajoutez le package de middleware Swagger approprié pour votre framework. Pour Gin :

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

Pour Echo :

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

Pour Fiber :

go get -u github.com/gofiber/swagger

Configuration de base

Commencez par ajouter des informations générales sur l’API dans votre fichier main.go. De manière similaire à la façon dont vous structureriez une API REST en Go, les annotations doivent être claires et descriptives :

// @title           Product API
// @version         1.0
// @description     Une API de gestion de produits avec documentation Swagger
// @termsOfService  http://swagger.io/terms/

// @contact.name   Support 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 Typez "Bearer" suivi d'un espace et d'un jeton JWT.

func main() {
    // Votre code d'application
}

Implémentation avec le framework Gin

Implémentons un exemple complet en utilisant Gin. Tout d’abord, définissons vos modèles de données avec des balises struct :

type Product struct {
    ID          int     `json:"id" example:"1"`
    Name        string  `json:"name" example:"Laptop" binding:"required"`
    Description string  `json:"description" example:"Ordinateur de haute performance"`
    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:"Entrée invalide"`
    Message string `json:"message" example:"Le nom du produit est requis"`
}

Maintenant, annotatez vos fonctions de gestionnaire. Lorsque vous travaillez avec opérations de base de données, ces annotations aident à documenter le flux de données :

// GetProduct godoc
// @Summary      Obtenir un produit par ID
// @Description  Récupérer un seul produit par son identifiant unique
// @Tags         produits
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "ID du produit"
// @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")
    // Implémentation ici
    c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

// CreateProduct godoc
// @Summary      Créer un nouveau produit
// @Description  Ajouter un nouveau produit au catalogue
// @Tags         produits
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "Objet produit"
// @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: "Mauvaise requête", Message: err.Error()})
        return
    }
    // Enregistrer dans la base de données
    c.JSON(201, product)
}

Génération de la documentation

Après avoir annoté votre code, générez la documentation Swagger :

swag init

Cela crée un dossier docs avec swagger.json, swagger.yaml et des fichiers Go. Importez et enregistrez le point de terminaison Swagger :

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    
    _ "yourproject/docs" // Import des docs générés
)

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

Maintenant, accédez à votre documentation API interactive à http://localhost:8080/swagger/index.html.

Implémentation avec le framework Echo

Les utilisateurs Echo suivent un modèle similaire mais avec un middleware spécifique à Echo :

package main

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

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

Implémentation avec le framework Fiber

L’implémentation avec Fiber est tout aussi simple :

package main

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

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

Annotations Swagger avancées

Documenter des corps de requête complexes

Pour les structures imbriquées ou les tableaux :

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      Créer une nouvelle commande
// @Description  Créer une commande avec plusieurs éléments et des informations d'expédition
// @Tags         commandes
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "Détails de la commande"
// @Success      201    {object}  Order
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // Implémentation
}

Documenter les téléchargements de fichiers

// UploadImage godoc
// @Summary      Télécharger une image de produit
// @Description  Télécharger un fichier image pour un produit
// @Tags         produits
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "ID du produit"
// @Param        file  formData  file  true  "Fichier image"
// @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")
    // Gérer le téléchargement
}

Paramètres de requête et pagination

// ListProducts godoc
// @Summary      Liste des produits avec pagination
// @Description  Obtenir une liste paginée des produits avec un filtre optionnel
// @Tags         produits
// @Accept       json
// @Produce      json
// @Param        page      query     int     false  "Numéro de page" default(1)
// @Param        page_size query     int     false  "Items par page" default(10)
// @Param        category  query     string  false  "Filtrer par catégorie"
// @Param        min_price query     number  false  "Prix minimum"
// @Param        max_price query     number  false  "Prix maximum"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // Implémentation avec pagination
}

Authentification et sécurité

Documentez différentes méthodes d’authentification dans votre API. Pour les applications multi-locataires, une documentation d’authentification appropriée est cruciale :

Authentification par jeton Bearer

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Tapez "Bearer" suivi d'un espace et d'un jeton JWT.

Authentification par clé API

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description Clé API pour l'authentification

Authentification OAuth2

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Accorde l'accès en écriture
// @scope.admin Accorde l'accès en lecture et en écriture aux informations administratives

Authentification basique

// @securityDefinitions.basic BasicAuth

Appliquez la sécurité à des points de terminaison spécifiques :

// @Security Bearer
// @Security ApiKeyAuth

Personnalisation de l’interface Swagger UI

Vous pouvez personnaliser l’apparence et le comportement de l’interface Swagger UI :

// Configuration personnalisée
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))

// Avec un titre personnalisé
r.GET("/swagger/*any", ginSwagger.WrapHandler(
    swaggerFiles.Handler,
    ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
    ginSwagger.DefaultModelsExpandDepth(-1),
))

Pour désactiver Swagger en production :

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

Intégration avec CI/CD

Automatisez la génération de la documentation Swagger dans votre pipeline CI/CD :

# Exemple GitHub Actions
name: Générer la documentation Swagger
on: [push]

jobs:
  swagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Configurer Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Installer swag
        run: go install github.com/swaggo/swag/cmd/swag@latest
      
      - name: Générer la documentation Swagger
        run: swag init
      
      - name: Commiter les docs
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add docs/
          git commit -m "Mettre à jour la documentation Swagger" || exit 0
          git push          

Bonnes pratiques

1. Style d’annotation cohérent

Maintenez un formatage cohérent sur tous les points de terminaison :

// HandlerName godoc
// @Summary      Description brève (moins de 50 caractères)
// @Description  Description détaillée de ce que fait le point de terminaison
// @Tags         nom-de-la-ressource
// @Accept       json
// @Produce      json
// @Param        nom  emplacement  type  requis  "description"
// @Success      200   {object}  TypeDeRéponse
// @Failure      400   {object}  ErrorResponse
// @Router       /chemin [méthode]

2. Utiliser des exemples descriptifs

Ajoutez des exemples réalistes pour aider les utilisateurs de l’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. Documenter tous les codes de réponse

Incluez tous les codes d’état HTTP possibles :

// @Success      200  {object}  Product
// @Success      201  {object}  Product
// @Failure      400  {object}  ErrorResponse "Mauvaise requête"
// @Failure      401  {object}  ErrorResponse "Non autorisé"
// @Failure      403  {object}  ErrorResponse "Interdit"
// @Failure      404  {object}  ErrorResponse "Non trouvé"
// @Failure      422  {object}  ErrorResponse "Erreur de validation"
// @Failure      500  {object}  ErrorResponse "Erreur interne du serveur"

4. Versionnez votre API

Utilisez une version correcte dans le chemin de base :

// @BasePath  /api/v1

Et organisez votre code en conséquence :

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

5. Groupez les points de terminaison liés

Utilisez des balises pour organiser logiquement les points de terminaison :

// @Tags produits
// @Tags commandes
// @Tags utilisateurs

6. Gardez la documentation à jour

Exécutez swag init avant chaque commit ou intégrez-le dans votre processus de construction :

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

Test de la documentation Swagger

Lorsque vous travaillez avec des architectures serverless comme AWS Lambda, le test de votre documentation API devient encore plus important :

func TestSwaggerGeneration(t *testing.T) {
    // Vérifiez que swagger.json existe
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("swagger.json non trouvé, exécutez 'swag init'")
    }
    
    // Vérifiez que le point de terminaison swagger répond
    r := setupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

Problèmes courants et solutions

Documentation non mise à jour

Si les modifications ne s’affichent pas, assurez-vous de régénérer les docs :

swag init --parseDependency --parseInternal

Le drapeau --parseDependency analyse les dépendances externes, et --parseInternal analyse les packages internes.

Types personnalisés non reconnus

Pour les types provenant de packages externes, utilisez le tag swaggertype :

type CustomTime struct {
    time.Time
}

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

Ou utilisez le tag swaggertype :

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

Tableaux et énumérations

Documentez les types de tableau et les énumérations :

type Filter struct {
    Status []string `json:"status" enums:"actif,inactif,en attente"`
    Tags   []string `json:"tags"`
}

// @Param  status  query  string  false  "Filtre d'état" Enums(actif, inactif, en attente)

Approches alternatives

Bien que swaggo soit le choix le plus populaire, d’autres options existent :

go-swagger

Une alternative plus riche en fonctionnalités mais plus complexe :

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

Fichiers OpenAPI manuels

Pour un contrôle complet, écrivez manuellement les spécifications OpenAPI en YAML :

openapi: 3.0.0
info:
  title: API de produits
  version: 1.0.0
paths:
  /products:
    get:
      summary: Liste des produits
      responses:
        '200':
          description: Succès

Puis servez avec :

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

Intégration avec l’IA et les LLM

Lors de la création d’API qui s’intègrent à des services d’IA, une bonne documentation devient cruciale. Par exemple, lors du travail avec sorties structurées des LLM, Swagger aide à documenter les schémas de requête et de réponse complexes :

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"Résumez ce texte"`
    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      Générer une sortie structurée de LLM
// @Description  Générer du texte avec un schéma de sortie contraint
// @Tags         llm
// @Accept       json
// @Produce      json
// @Param        request  body      LLMRequest  true  "Paramètres LLM"
// @Success      200      {object}  map[string]interface{}
// @Failure      400      {object}  ErrorResponse
// @Router       /llm/generate [post]
func GenerateStructured(c *gin.Context) {
    // Implémentation
}

Considérations de performance

La documentation Swagger a un impact minimal sur les performances :

  • Temps de construction : swag init prend 1 à 3 secondes pour la plupart des projets
  • Temps d’exécution : La documentation est chargée une seule fois au démarrage
  • Mémoire : Ajoute généralement 1 à 2 Mo à la taille du binaire
  • Temps de réponse : Aucun impact sur les points de terminaison API eux-mêmes

Pour les API très grandes (100+ points de terminaison), envisagez :

  • De diviser en plusieurs fichiers Swagger
  • De charger Swagger UI de manière différée
  • De servir la documentation depuis un service séparé

Considérations de sécurité

Lors de l’exposition de la documentation Swagger :

  1. Désactiver en production (si l’API est interne) :
if os.Getenv("ENV") == "production" {
    // Ne pas enregistrer le point de terminaison Swagger
    return
}
  1. Ajouter une authentification :
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Limiter le taux de requêtes pour le point de terminaison :
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Ne jamais exposer les détails internes :
  • Ne documentez pas les points de terminaison internes
  • Évitez d’exposer directement les schémas de base de données
  • Nettoyez les messages d’erreur dans la documentation

Conclusion

Ajouter une documentation Swagger à votre API Go transforme l’expérience du développeur de devineries en exploration guidée. La bibliothèque swaggo rend ce processus simple en générant automatiquement une documentation OpenAPI complète à partir de vos annotations de code.

Points clés :

  • Commencez avec des annotations de base et développez progressivement
  • Gardez la documentation synchronisée avec le code via CI/CD
  • Utilisez l’interface Swagger UI pour le test interactif pendant le développement
  • Documentez soigneusement l’authentification, les erreurs et les cas limites
  • Pensez aux implications de sécurité lors de l’exposition de la documentation

Qu’il s’agisse de microservices, d’API publiques ou d’outils internes, la documentation Swagger rapporte des bénéfices en réduisant le besoin de support, en accélérant l’intégration et en améliorant la conception des API. L’investissement initial pour apprendre la syntaxe des annotations devient rapidement routinier, et la génération automatisée assure que votre documentation ne reste jamais en arrière de votre implémentation.

Pour les développeurs Go, la combinaison de typage fort, de génération de code et du système d’annotations de swaggo crée un flux de travail puissant qui rend la documentation API une partie naturelle du processus de développement plutôt qu’une après-pensée.

Liens utiles

Ressources externes