Swagger toevoegen aan je Go API

OpenAPI-documentatie automatisch genereren op basis van codeannotaties

Inhoud

API-documentatie is essentieel voor elke moderne toepassing, en voor Go API’s Swagger (OpenAPI) is geworden de industrie-standaard. Voor Go-ontwikkelaars biedt swaggo een elegante oplossing om uitgebreide API-documentatie direct te genereren uit code-annotaties.

swagger api specs op agile board Deze mooie afbeelding wordt gegenereerd door AI model Flux 1 dev.

Waarom Swagger belangrijk is voor Go API’s

Bij het bouwen van REST API’s wordt documentatie vaak verouderd als de code evolueert. Swagger lost dit op door documentatie te genereren uit je broncode, waardoor het in synchronisatie blijft met je implementatie. Het interactieve Swagger UI laat ontwikkelaars eindpunten rechtstreeks vanuit de browser testen, wat het ontwikkelaarservaring aanzienlijk verbetert.

Voor teams die microservices of openbare API’s bouwen, wordt Swagger-documentatie essentieel voor:

  • Clientgeneratie: Creëer automatisch clientbibliotheken in meerdere talen
  • Contracttesten: Valideer aanvragen en reacties tegen gedefinieerde schema’s
  • Teamcoördinatie: Geef een enkele bron van waarheid voor API-contracten
  • Ontwikkelaarsopvang: Nieuwe teamleden kunnen API’s interactief verkennen

Aan de slag met swaggo

De swaggo-bibliotheek is de populairste tool om Swagger-ondersteuning toe te voegen aan Go-toepassingen. Het werkt door speciale commentaren in je code te parsen en OpenAPI 3.0-specifiekbestanden te genereren.

Installatie

Installeer eerst het swag CLI-hulpprogramma:

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

Voeg vervolgens de juiste Swagger-middlewarebibliotheek toe voor je framework. Voor Gin:

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

Voor Echo:

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

Voor Fiber:

go get -u github.com/gofiber/swagger

Basisconfiguratie

Begin met het toevoegen van algemene API-informatie in je main.go-bestand. Net zoals je zou doen bij het structureren van een REST API in Go, moeten de annotaties duidelijk en beschrijvend zijn:

// @title           Product API
// @version         1.0
// @description     Een productbeheer API met Swagger-documentatie
// @termsOfService  http://swagger.io/terms/

// @contact.name   API Ondersteuning
// @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 Type "Bearer" gevolgd door een spatie en JWT-token.

func main() {
    // Je applicatiecode
}

Implementatie met Gin-framework

Laten we een volledig voorbeeld implementeren met Gin. Definieer eerst je datamodellen met struct-tags:

type Product struct {
    ID          int     `json:"id" example:"1"`
    Naam        string  `json:"naam" example:"Laptop" binding:"required"`
    Beschrijving string  `json:"beschrijving" example:"Hoogprestatie laptop"`
    Prijs       float64 `json:"prijs" example:"999.99" binding:"required,gt=0"`
    Voorraad    int     `json:"voorraad" example:"50"`
}

type ErrorResponse struct {
    Fout   string `json:"fout" example:"Ongeldige invoer"`
    Bericht string `json:"bericht" example:"Productnaam is vereist"`
}

Annoteer nu je handlerfuncties. Bij het werken met databasebewerkingen, helpen deze annotaties bij het documenteren van de gegevensstroom:

// GetProduct godoc
// @Summary      Product ophalen op basis van ID
// @Description  Haal een enkel product op op basis van zijn unieke identificator
// @Tags         producten
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "Product ID"
// @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")
    // Implementatie hier
    c.JSON(200, Product{ID: 1, Naam: "Laptop", Prijs: 999.99})
}

// CreateProduct godoc
// @Summary      Een nieuw product aanmaken
// @Description  Voeg een nieuw product toe aan het catalogus
// @Tags         producten
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "Productobject"
// @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{Fout: "Foutieve aanvraag", Bericht: err.Error()})
        return
    }
    // Opslaan in database
    c.JSON(201, product)
}

Documentatie genereren

Na het annoteren van je code, genereer de Swagger-documentatie:

swag init

Dit maakt een docs-map met swagger.json, swagger.yaml en Go-bestanden. Importeer en registreer het Swagger-eindpunt:

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    
    _ "yourproject/docs" // Importeer gegenereerde docs
)

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

Nu kun je je interactieve API-documentatie bezoeken op http://localhost:8080/swagger/index.html.

Implementatie met Echo-framework

Echo-gebruikers volgen een vergelijkbaar patroon maar met Echo-specifieke middleware:

package main

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

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

Implementatie met Fiber-framework

Fibers implementatie is net zo eenvoudig:

package main

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

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

Geavanceerde Swagger-annotaties

Documenteren van complexe aanvraaglichamen

Voor geneste structuren of arrays:

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"`
    Aantal    int `json:"aantal" example:"2" binding:"required,min=1"`
}

type Address struct {
    Straat  string `json:"straat" example:"123 Main St" binding:"required"`
    Stad    string `json:"stad" example:"New York" binding:"required"`
    Postcode string `json:"postcode" example:"10001" binding:"required"`
}

// CreateOrder godoc
// @Summary      Een nieuw bestelling aanmaken
// @Description  Maak een bestelling met meerdere items en verzendinformatie
// @Tags         bestellingen
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "Bestelgegevens"
// @Success      201    {object}  Bestelling
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // Implementatie
}

Documenteren van bestandsuploads

// UploadImage godoc
// @Summary      Productafbeelding uploaden
// @Description  Upload een afbeeldingsbestand voor een product
// @Tags         producten
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "Product ID"
// @Param        bestand  formData  file  true  "Afbeeldingsbestand"
// @Success      200   {object}  map[string]string
// @Failure      400   {object}  ErrorResponse
// @Security     Bearer
// @Router       /products/{id}/image [post]
func UploadImage(c *gin.Context) {
    bestand, _ := c.FormFile("bestand")
    // Uploadverwerking
}

Queryparameters en pagineren

// ListProducts godoc
// @Summary      Productenlijst met pagineren
// @Description  Ontvang een paginerende lijst van producten met optionele filteren
// @Tags         producten
// @Accept       json
// @Produce      json
// @Param        pagina      query     int     false  "Paginanummer" default(1)
// @Param        pagina_grootte query     int     false  "Items per pagina" default(10)
// @Param        categorie  query     string  false  "Filter op categorie"
// @Param        min_prijs query     number  false  "Minimale prijs"
// @Param        max_prijs query     number  false  "Maximale prijs"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // Implementatie met pagineren
}

Authenticatie en beveiliging

Documenteer verschillende authenticatiemethoden in je API. Voor multi-tenant toepassingen, is juiste authenticatie-documentatie cruciaal:

Bearer Token Authenticatie

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" gevolgd door een spatie en JWT-token.

API-sleutel authenticatie

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description API-sleutel voor authenticatie

OAuth2 authenticatie

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Schrijfrechten verlenen
// @scope.admin Schrijf- en leesrechten verlenen voor administratieve informatie

Basisauthenticatie

// @securityDefinitions.basic BasicAuth

Toepassen van beveiliging op specifieke eindpunten:

// @Security Bearer
// @Security ApiKeyAuth

Aanpassen van Swagger UI

Je kunt het uiterlijk en gedrag van de Swagger UI aanpassen:

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

// Met aangepast titel
r.GET("/swagger/*any", ginSwagger.WrapHandler(
    swaggerFiles.Handler,
    ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
    ginSwagger.DefaultModelsExpandDepth(-1),
))

Swagger uitschakelen in productie:

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

Integreren met CI/CD

Automatiseer de generatie van Swagger-documentatie in je CI/CD-pijplijn:

# GitHub Actions-voorbeeld
naam: Genereer Swagger Docs
op: [push]

taken:
  swagger:
    runs-on: ubuntu-latest
    stappen:
      - gebruikt: actions/checkout@v3
      
      - naam: Stel Go in
        gebruikt: actions/setup-go@v4
        met:
          go-versie: '1.21'
      
      - naam: Installeer swag
        uitvoeren: go install github.com/swaggo/swag/cmd/swag@latest
      
      - naam: Genereer Swagger docs
        uitvoeren: swag init
      
      - naam: Commit docs
        uitvoeren: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add docs/
          git commit -m "Update Swagger-documentatie" || exit 0
          git push          

Beste praktijken

1. Consistente annotatiestijl

Houd een consistente opmaak over alle eindpunten:

// HandlerNaam godoc
// @Summary      Korte beschrijving (onder 50 tekens)
// @Description  Gedetailleerde beschrijving van wat het eindpunt doet
// @Tags         bronnaam
// @Accept       json
// @Produce      json
// @Param        naam  locatie  type  vereist  "beschrijving"
// @Success      200   {object}  ResponseType
// @Failure      400   {object}  ErrorResponse
// @Router       /pad [methode]

2. Gebruik beschrijvende voorbeelden

Voeg realistische voorbeelden toe om API-gebruikers te helpen:

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

3. Documenteer alle responscodes

Voeg alle mogelijke HTTP-statuscodes toe:

// @Success      200  {object}  Product
// @Success      201  {object}  Product
// @Failure      400  {object}  ErrorResponse "Foutieve aanvraag"
// @Failure      401  {object}  ErrorResponse "Niet geautoriseerd"
// @Failure      403  {object}  ErrorResponse "Verboden"
// @Failure      404  {object}  ErrorResponse "Niet gevonden"
// @Failure      422  {object}  ErrorResponse "Validatiefout"
// @Failure      500  {object}  ErrorResponse "Interne serverfout"

4. Versieer je API

Gebruik juiste versieering in basispad:

// @BasePath  /api/v1

En organiseer je code overeenkomstig:

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

5. Groepeer verwante eindpunten

Gebruik tags om eindpunten logisch te organiseren:

// @Tags producten
// @Tags bestellingen
// @Tags gebruikers

6. Houd de documentatie up-to-date

Voer swag init uit voor elke commit of integreer het in je bouwproces:

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

Testen van Swagger-documentatie

Bij het werken met serverloze architecturen zoals AWS Lambda, wordt het testen van je API-documentatie nog belangrijker:

func TestSwaggerGeneration(t *testing.T) {
    // Controleer of swagger.json bestaat
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("swagger.json niet gevonden, voer 'swag init' uit")
    }
    
    // Controleer of swagger-eindpunt reageert
    r := setupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

Veelvoorkomende problemen en oplossingen

Documentatie die niet bijwerkt

Als wijzigingen niet verschijnen, zorg er dan voor dat je de documentatie opnieuw genereert:

swag init --parseDependency --parseInternal

De --parseDependency-vlag parseert externe afhankelijkheden, en --parseInternal parseert interne pakketten.

Aangepaste typen niet herkend

Voor typen van externe pakketten, gebruik de swaggertype-tag:

type CustomTime struct {
    time.Time
}

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

Of gebruik de swaggertype-tag:

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

Arrays en enums

Documenteer arraytypen en enums:

type Filter struct {
    Status []string `json:"status" enums:"actief,inactief,uitstaand"`
    Tags   []string `json:"tags"`
}

// @Param  status  query  string  false  "Statusfilter" Enums(actief, inactief, uitstaand)

Alternatieve aanpakken

Hoewel swaggo de populairste keuze is, bestaan er ook andere opties:

go-swagger

Een meer functie-rijke maar complexere alternatief:

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

Handmatige OpenAPI-bestanden

Voor volledige controle, schrijf OpenAPI-specificaties handmatig in YAML:

openapi: 3.0.0
info:
  title: Product API
  version: 1.0.0
paths:
  /products:
    get:
      summary: Productenlijst
      responses:
        '200':
          description: Succes

Dan dienen met:

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

Integratie met AI en LLMs

Bij het bouwen van API’s die integreren met AI-diensten, wordt juiste documentatie cruciaal. Bijvoorbeeld, bij het werken met gestructureerde LLM-uitvoer, helpt Swagger bij het documenteren van complexe aanvraag- en reactieschema’s:

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"Deze tekst samenvatten"`
    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      Genereer gestructureerde LLM-uitvoer
// @Description  Genereer tekst met beperkte uitvoerschema
// @Tags         llm
// @Accept       json
// @Produce      json
// @Param        request  body      LLMRequest  true  "LLM-parameters"
// @Success      200      {object}  map[string]interface{}
// @Failure      400      {object}  ErrorResponse
// @Router       /llm/generate [post]
func GenerateStructured(c *gin.Context) {
    // Implementatie
}

Prestatieoverwegingen

Swagger-documentatie heeft minimaal invloed op prestaties:

  • Buildtijd: swag init duurt 1-3 seconden voor de meeste projecten
  • Runtime: Documentatie wordt één keer bij het opstarten geladen
  • Geheugen: Voegt meestal 1-2MB toe aan de binaire grootte
  • Responsentijd: Geen invloed op API-eindpunten zelf

Voor zeer grote API’s (100+ eindpunten), overweeg:

  • Het splitsen in meerdere Swagger-bestanden
  • Lazy-loading van Swagger UI-assets
  • Het serveren van documentatie vanaf een aparte service

Beveiligingsoverwegingen

Bij het blootstellen van Swagger-documentatie:

  1. Uitschakelen in productie (als de API intern is):
if os.Getenv("ENV") == "productie" {
    // Register Swagger-eindpunt niet
    return
}
  1. Authenticatie toevoegen:
geauthificeerd := r.Group("/swagger")
geauthificeerd.Use(AuthMiddleware())
geauthificeerd.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Beperk het eindpunt:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Nooit interne details blootgeven:
  • Documenteer geen interne eindpunten
  • Vermijd het blootgeven van database-schema’s
  • Sanitize foutmeldingen in documentatie

Conclusie

Het toevoegen van Swagger-documentatie aan je Go API verandert de ontwikkelaarservaring van gokken naar geleide verkennings. De swaggo-bibliotheek maakt dit proces eenvoudig door uitgebreide OpenAPI-documentatie te genereren vanuit je code-annotaties.

Belangrijke conclusies:

  • Start met basisannotaties en breid geleidelijk uit
  • Houd documentatie in synchronisatie met code via CI/CD
  • Gebruik Swagger UI voor interactief testen tijdens ontwikkeling
  • Documenteer authenticatie, fouten en randgevallen grondig
  • Overweeg beveiligingsimplicaties bij het blootstellen van documentatie

Of je nu microservices, openbare API’s of interne tools bouwt, levert Swagger-documentatie dividend in het verminderen van ondersteuningslast, snellere opvang en betere API-design. De initiële investering in het leren van annotatiesyntax wordt snel routine, en automatische generatie zorgt ervoor dat je documentatie nooit achterblijft bij je implementatie.

Voor Go-ontwikkelaars creëert de combinatie van sterke typing, codegeneratie en de swaggo-annotatiesysteem een krachtige workflow die API-documentatie een natuurlijk onderdeel van het ontwikkelproces maakt in plaats van een na-actie.

Externe bronnen