Dodawanie Swagger do swojej API w Go

Automatyczne generowanie dokumentacji OpenAPI na podstawie adnotacji w kodzie

Page content

Dokumentacja API jest kluczowa dla każdej współczesnej aplikacji, a dla Go APIs Swagger (OpenAPI) stała się standardem branżowym. Dla programistów Go, swaggo oferuje eleganckie rozwiązanie do generowania szczegółowej dokumentacji API bezpośrednio z adnotacji w kodzie.

swagger api specs on agile board To piękne zdjęcie zostało wygenerowane przez AI model Flux 1 dev.

Dlaczego Swagger ma znaczenie dla API w Go

Podczas budowania REST API dokumentacja często staje się przestarzała, gdy kod ewoluuje. Swagger rozwiązuje to, generując dokumentację z Twojego kodu źródłowego, zapewniając, że pozostaje zsynchronizowana z implementacją. Interaktywny interfejs Swagger UI umożliwia programistom testowanie punktów końcowych bezpośrednio z przeglądarki, znacząco poprawiając doświadczenie dewelopera.

Dla zespołów tworzących mikroserwisy lub publiczne API, dokumentacja Swagger staje się niezwykle ważna, ponieważ:

  • Generowanie klienta: automatycznie tworzy biblioteki klienta w wielu językach
  • Testowanie kontraktów: waliduje żądania i odpowiedzi względem zdefiniowanych schematów
  • Współpraca w zespole: dostarcza jednego źródła prawdy dla kontraktów API
  • Onboarding deweloperów: nowi członkowie zespołu mogą interaktywnie eksplorować API

Rozpoczynanie pracy z swaggo

Biblioteka swaggo jest najpopularniejszym narzędziem do dodawania obsługi Swagger do aplikacji w Go. Działa ona poprzez analizowanie specjalnych komentarzy w kodzie i generowanie plików specyfikacji OpenAPI 3.0.

Instalacja

Najpierw zainstaluj narzędzie CLI swag:

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

Następnie dodaj odpowiedni pakiet middleware Swagger dla swojej ramki. Dla Gin:

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

Dla Echo:

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

Dla Fiber:

go get -u github.com/gofiber/swagger

Podstawowa konfiguracja

Zacznij od dodania ogólnych informacji o API w swoim pliku main.go. Podobnie jak w przypadku struktury REST API w Go, adnotacje powinny być jasne i opisowe:

// @title           Product API
// @version         1.0
// @description     API zarządzania produktami z dokumentacją Swagger
// @termsOfService  http://swagger.io/terms/

// @contact.name   Obsługa 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 Wpisz "Bearer" połączony znakiem spacji i token JWT.

func main() {
    // Twój kod aplikacji
}

Implementacja z użyciem ramki Gin

Zaimplementujmy kompletny przykład za pomocą Gin. Najpierw zdefiniuj swoje modele danych z tagami struct:

type Product struct {
    ID          int     `json:"id" example:"1"`
    Name        string  `json:"name" example:"Laptop" binding:"required"`
    Description string  `json:"description" example:"Wysokowydajny laptop"`
    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:"Nieprawidłowe dane wejściowe"`
    Message string `json:"message" example:"Nazwa produktu jest wymagana"`
}

Teraz adnotuj swoje funkcje obsługujące żądania. Gdy pracujesz z operacjami bazowymi danych, te adnotacje pomagają dokumentować przepływ danych:

// GetProduct godoc
// @Summary      Pobierz produkt według ID
// @Description  Pobierz pojedynczy produkt według jego unikalnego identyfikatora
// @Tags         produkty
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "ID produktu"
// @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")
    // Implementacja tutaj
    c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

// CreateProduct godoc
// @Summary      Utwórz nowy produkt
// @Description  Dodaj nowy produkt do katalogu
// @Tags         produkty
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "Obiekt produktu"
// @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: "Zły żądanie", Message: err.Error()})
        return
    }
    // Zapis do bazy danych
    c.JSON(201, product)
}

Generowanie dokumentacji

Po adnotowaniu swojego kodu, wygeneruj dokumentację Swagger:

swag init

To tworzy folder docs z plikami swagger.json, swagger.yaml i plikami Go. Zaimportuj i zarejestruj punkt końcowy Swagger:

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    
    _ "yourproject/docs" // Importuj wygenerowane dokumenty
)

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

Teraz odwiedź swoją interaktywną dokumentację API pod adresem http://localhost:8080/swagger/index.html.

Implementacja z użyciem ramki Echo

Użytkownicy Echo stosują podobny wzór, ale z middleware specyficzny dla Echo:

package main

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

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

Implementacja z użyciem ramki Fiber

Implementacja Fiber jest równie prosta:

package main

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

func main() {
    app := fiber.New()
    
    // Trasy API
    api := app.Group("/api/v1")
    api.Get("/products/:id", getProduct)
    api.Post("/products", createProduct)
    
    // Punkt końcowy Swagger
    app.Get("/swagger/*", swagger.HandlerDefault)
    
    app.Listen(":8080")
}

Zaawansowane adnotacje Swagger

Dokumentowanie złożonych treści żądania

Dla struktur zagnieżdżonych lub tablic:

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:"Nowy Jork" binding:"required"`
    ZipCode string `json:"zip_code" example:"10001" binding:"required"`
}

// CreateOrder godoc
// @Summary      Utwórz nowe zamówienie
// @Description  Utwórz zamówienie z wieloma elementami i informacjami o dostawie
// @Tags         zamówienia
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "Szczegóły zamówienia"
// @Success      201    {object}  Order
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // Implementacja
}

Dokumentowanie przesyłania plików

// UploadImage godoc
// @Summary      Prześlij obraz produktu
// @Description  Prześlij plik obrazowy dla produktu
// @Tags         produkty
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "ID produktu"
// @Param        file  formData  file  true  "Plik obrazowy"
// @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")
    // Obsługa przesyłania
}

Parametry zapytania i paginacja

// ListProducts godoc
// @Summary      Lista produktów z paginacją
// @Description  Pobierz paginowaną listę produktów z opcjonalnym filtrowaniem
// @Tags         produkty
// @Accept       json
// @Produce      json
// @Param        page      query     int     false  "Numer strony" default(1)
// @Param        page_size query     int     false  "Liczba elementów na stronę" default(10)
// @Param        category  query     string  false  "Filtruj według kategorii"
// @Param        min_price query     number  false  "Minimalna cena"
// @Param        max_price query     number  false  "Maksymalna cena"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // Implementacja z paginacją
}

Autoryzacja i bezpieczeństwo

Dokumentuj różne metody autoryzacji w swoim API. Dla aplikacji wieloudziałowych, odpowiednia dokumentacja autoryzacji jest kluczowa:

Autoryzacja tokenem Bearer

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Wpisz "Bearer" połączony znakiem spacji i token JWT.

Autoryzacja kluczem API

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description Klucz API do autoryzacji

Autoryzacja OAuth2

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Umożliwia dostęp do zapisu
// @scope.admin Umożliwia odczyt i zapis do informacji administracyjnych

Autoryzacja podstawowa

// @securityDefinitions.basic BasicAuth

Zastosuj zabezpieczenia do konkretnych punktów końcowych:

// @Security Bearer
// @Security ApiKeyAuth

Personalizacja interfejsu Swagger UI

Możesz dostosować wygląd i zachowanie interfejsu Swagger UI:

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

// Z wykorzystaniem niestandardowego tytułu
r.GET("/swagger/*any", ginSwagger.WrapHandler(
    swaggerFiles.Handler,
    ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
    ginSwagger.DefaultModelsExpandDepth(-1),
))

Aby wyłączyć Swagger w środowisku produkcyjnym:

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

Integracja z CI/CD

Automatyzuj generowanie dokumentacji Swagger w swoim pipeline CI/CD:

# Przykład GitHub Actions
name: Generuj dokumentację Swagger
on: [push]

jobs:
  swagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Ustaw Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Zainstaluj swag
        run: go install github.com/swaggo/swag/cmd/swag@latest
      
      - name: Generuj dokumentację Swagger
        run: swag init
      
      - name: Zatwierdź dokumentację
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add docs/
          git commit -m "Zaktualizowano dokumentację Swagger" || exit 0
          git push          

Najlepsze praktyki

1. Spójny styl adnotacji

Utrzymuj spójny styl formatowania na wszystkich punktach końcowych:

// HandlerName godoc
// @Summary      Krótki opis (mniej niż 50 znaków)
// @Description  Szczegółowy opis tego, co robi punkt końcowy
// @Tags         nazwa_zasobu
// @Accept       json
// @Produce      json
// @Param        nazwa  lokalizacja  typ  wymagane  "opis"
// @Success      200   {object}  TypOdpowiedzi
// @Failure      400   {object}  ErrorResponse
// @Router       /ścieżka [metoda]

2. Używanie opisowych przykładów

Dodaj realistyczne przykłady, aby pomóc użytkownikom 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. Dokumentuj wszystkie kody odpowiedzi

Zawieraj wszystkie możliwe kody HTTP:

// @Success      200  {object}  Product
// @Success      201  {object}  Product
// @Failure      400  {object}  ErrorResponse "Zły żądanie"
// @Failure      401  {object}  ErrorResponse "Nieautoryzowany"
// @Failure      403  {object}  ErrorResponse "Zabronione"
// @Failure      404  {object}  ErrorResponse "Nie znaleziono"
// @Failure      422  {object}  ErrorResponse "Błąd walidacji"
// @Failure      500  {object}  ErrorResponse "Błąd wewnętrzny serwera"

4. Wersjonuj swój API

Używaj odpowiedniego wersjonowania w podstawowej ścieżce:

// @BasePath  /api/v1

I organizuj swój kod odpowiednio:

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

5. Grupuj powiązane punkty końcowe

Używaj tagów, aby logicznie organizować punkty końcowe:

// @Tags produkty
// @Tags zamówienia
// @Tags użytkownicy

6. Utrzymuj aktualną dokumentację

Uruchamiaj swag init przed każdym commitem lub zintegruj ją z procesem budowania:

#!/bin/bash
# hook przed commitem
swag init
git add docs/

Testowanie dokumentacji Swagger

Gdy pracujesz z architekturami bezserwerowymi, takimi jak AWS Lambda, testowanie dokumentacji API staje się jeszcze ważniejsze:

func TestSwaggerGeneration(t *testing.T) {
    // Sprawdź, czy plik swagger.json istnieje
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("Nie znaleziono swagger.json, uruchom 'swag init'")
    }
    
    // Sprawdź, czy punkt końcowy swagger odpowiada
    r := setupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

Typowe problemy i rozwiązania

Dokumentacja nieaktualizuje się

Jeśli zmiany nie pojawiają się, upewnij się, że ponownie generujesz dokumentację:

swag init --parseDependency --parseInternal

Flaga --parseDependency analizuje zależności zewnętrzne, a --parseInternal analizuje pakiety wewnętrzne.

Nierejestrowane typy niestandardowe

Dla typów z pakietów zewnętrznych, użyj tagu swaggertype:

type CustomTime struct {
    time.Time
}

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

Lub użyj tagu swaggertype:

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

Tablice i enumery

Dokumentuj typy tablicowe i enumery:

type Filter struct {
    Status []string `json:"status" enums:"aktywny,nieaktywny,w toku"`
    Tags   []string `json:"tags"`
}

// @Param  status  query  string  false  "Filtr statusu" Enums(aktywny, nieaktywny, w toku)

Alternatywne podejścia

Choć swaggo jest najpopularniejszym wyborem, istnieją inne opcje:

go-swagger

Bardziej zaawansowana, ale bardziej skomplikowana alternatywa:

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

Ręczne pliki OpenAPI

Dla pełnej kontroli, ręcznie napisz specyfikacje OpenAPI w YAML:

openapi: 3.0.0
info:
  title: API Produktu
  version: 1.0.0
paths:
  /products:
    get:
      summary: Lista produktów
      responses:
        '200':
          description: Sukces

Następnie służy:

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

Integracja z AI i LLM

Gdy budujesz API, które integrują się z usługami AI, odpowiednia dokumentacja staje się kluczowa. Na przykład, gdy pracujesz z strukturalnymi wyjściami LLM, Swagger pomaga dokumentować złożone schematy żądań i odpowiedzi:

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"Podsumuj ten tekst"`
    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      Generuj strukturalne wyjście LLM
// @Description  Generuj tekst z ograniczonym schematem wyjścia
// @Tags         llm
// @Accept       json
// @Produce      json
// @Param        request  body      LLMRequest  true  "Parametry LLM"
// @Success      200      {object}  map[string]interface{}
// @Failure      400      {object}  ErrorResponse
// @Router       /llm/generate [post]
func GenerateStructured(c *gin.Context) {
    // Implementacja
}

Rozważenia dotyczące wydajności

Dokumentacja Swagger ma minimalny wpływ na wydajność:

  • Czas budowania: swag init zajmuje 1-3 sekundy dla większości projektów
  • Czas działania: Dokumentacja jest ładowana tylko raz przy starcie
  • Pamięć: Zwykle dodaje 1-2 MB do rozmiaru binarki
  • Czas odpowiedzi: Brak wpływu na same punkty końcowe API

Dla bardzo dużych API (ponad 100 punktów końcowych), rozważ:

  • Podział na wiele plików Swagger
  • Opóźnione ładowanie zasobów interfejsu Swagger
  • Serwowanie dokumentacji z osobnego serwisu

Rozważania dotyczące bezpieczeństwa

Gdy udostępniasz dokumentację Swagger:

  1. Wyłącz w środowisku produkcyjnym (jeśli API jest wewnętrzne):
if os.Getenv("ENV") == "production" {
    // Nie rejestruj punktu końcowego Swagger
    return
}
  1. Dodaj autoryzację:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Ogranicz przepustowość punktu końcowego:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Nigdy nie ujawniaj szczegółów wewnętrznych:
  • Nie dokumentuj wewnętrznych punktów końcowych
  • Unikaj bezpośredniego ujawniania schematów bazy danych
  • Zwalczaj komunikaty o błędach w dokumentacji

Podsumowanie

Dodanie dokumentacji Swagger do swojego API w Go przekształca doświadczenie dewelopera z domysłów na eksplorację kierowaną. Biblioteka swaggo sprawia, że ten proces jest prosty, generując szczegółową dokumentację OpenAPI z Twoich adnotacji w kodzie.

Główne wnioski:

  • Zacznij od podstawowych adnotacji i stopniowo rozwijaj je
  • Utrzymuj synchronizację dokumentacji z kodem za pomocą CI/CD
  • Używaj interaktywnego interfejsu Swagger do testowania podczas rozwoju
  • Dokumentuj autoryzację, błędy i przypadki graniczne dokładnie
  • Rozważ konsekwencje bezpieczeństwa przy udostępnianiu dokumentacji

Nie ważne, czy budujesz mikroserwisy, publiczne API czy narzędzia wewnętrzne, dokumentacja Swagger przynosi korzyści w zmniejszeniu obciążenia wsparcia, szybszym onboardingu i lepszym projektowaniu API. Początkowe inwestowanie w naukę składni adnotacji szybko staje się rutyną, a automatyczna generacja zapewnia, że dokumentacja nigdy nie zostaje za późna w stosunku do implementacji.

Dla programistów Go, połączenie silnego typowania, generacji kodu i systemu adnotacji swaggo tworzy potężny workflow, który sprawia, że dokumentacja API staje się naturalną częścią procesu rozwoju, a nie myślą pośród.

Przydatne linki

Zewnętrzne zasoby