Dodanie Swagger do Twojego API w Go

Automatyczne generowanie dokumentacji OpenAPI z adnotacji kodu

Page content

Dokumentacja API jest kluczowa dla każdego nowoczesnego oprogramowania, a dla API w Go Swagger (OpenAPI) stał się standardem branżowym. Dla deweloperów Go biblioteka swaggo oferuje eleganckie rozwiązanie do generowania kompleksowej dokumentacji API bezpośrednio z adnotacji w kodzie.

specyfikacje swagger na tablicy agile To ładne zdjęcie zostało wygenerowane przez model AI Flux 1 dev.

Dlaczego Swagger jest ważny dla API w Go

Przy budowaniu API REST dokumentacja często staje się nieaktualna wraz z ewolucją kodu. Swagger rozwiązuje ten problem, generując dokumentację bezpośrednio z kodu źródłowego, co zapewnia jej synchronizację z implementacją. Interaktywny interfejs Swagger UI pozwala deweloperom testować punkty końcowe (endpoints) bezpośrednio z przeglądarki, co znacząco poprawia doświadczenie deweloperskie.

Dla zespołów budujących mikrousługi lub publiczne API dokumentacja Swagger staje się niezbędna w zakresie:

  • Generowania klientów: Automatyczne tworzenie bibliotek klienckich w wielu językach
  • Testowania kontraktowego: Walidacja żądań i odpowiedzi zgodnie z zdefiniowanymi schematami
  • Współpracy zespołowej: Zapewnienie pojedynczego źródła prawdy dla kontraktów API
  • Onboardingu deweloperów: Nowi członkowie zespołu mogą eksplorować API w sposób interaktywny

Rozpoczęcie pracy z swaggo

Biblioteka swaggo to najpopularniejsze narzędzie do dodawania wsparcia dla Swagger w aplikacjach Go. Działa ona poprzez analizę specjalnych komentarzy w Twoim kodzie i generowanie plików ze specyfikacją 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 swojego frameworka. 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

Rozpocznij od dodania ogólnych informacji o API w pliku main.go. Podobnie jak to, w jaki sposób strukturyzujesz API REST w Go, adnotacje powinny być przejrzyste i opisowe:

// @title           Product API
// @version         1.0
// @description     A product management API with Swagger documentation
// @termsOfService  http://swagger.io/terms/

// @contact.name   API Support
// @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" followed by a space and JWT token.

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

Implementacja z frameworkiem Gin

Zaimplementujmy kompletny przykład, używając Gin. Najpierw zdefiniuj swoje modele danych z tagami struktury:

type Product struct {
    ID          int     `json:"id" example:"1"`
    Name        string  `json:"name" example:"Laptop" binding:"required"`
    Description string  `json:"description" example:"High-performance 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:"Invalid input"`
    Message string `json:"message" example:"Product name is required"`
}

Teraz dodaj adnotacje do funkcji obsługujących żądania (handlerów). Przy pracy z operacjami na bazie danych, te adnotacje pomagają w udokumentowaniu przepływu danych:

// GetProduct godoc
// @Summary      Get product by ID
// @Description  Retrieve a single product by its unique identifier
// @Tags         products
// @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")
    // Implementacja tutaj
    c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

// CreateProduct godoc
// @Summary      Create a new product
// @Description  Add a new product to the catalog
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "Product object"
// @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: "Bad Request", Message: err.Error()})
        return
    }
    // Zapis do bazy danych
    c.JSON(201, product)
}

Generowanie dokumentacji

Po adnotowaniu kodu wygeneruj dokumentację Swagger:

swag init

Działa to tworzy folder docs zawierający swagger.json, swagger.yaml oraz pliki 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" // Import wygenerowanej dokumentacji
)

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 uzyskaj dostęp do swojej interaktywnej dokumentacji API pod adresem http://localhost:8080/swagger/index.html.

Implementacja z frameworkiem Echo

Użytkownicy Echo stosują podobny wzorzec, ale z middleware specyficznym 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 frameworkiem Fiber

Implementacja z użyciem 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 ciał żądań

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

// CreateOrder godoc
// @Summary      Create a new order
// @Description  Create an order with multiple items and shipping information
// @Tags         orders
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "Order details"
// @Success      201    {object}  Order
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // Implementacja
}

Dokumentowanie wysyłania plików

// UploadImage godoc
// @Summary      Upload product image
// @Description  Upload an image file for a product
// @Tags         products
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "Product ID"
// @Param        file  formData  file  true  "Image file"
// @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 wysyłki
}

Parametry zapytania i paginacja

// ListProducts godoc
// @Summary      List products with pagination
// @Description  Get paginated list of products with optional filtering
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        page      query     int     false  "Page number" default(1)
// @Param        page_size query     int     false  "Items per page" default(10)
// @Param        category  query     string  false  "Filter by category"
// @Param        min_price query     number  false  "Minimum price"
// @Param        max_price query     number  false  "Maximum price"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // Implementacja z paginacją
}

Uwierzytelnianie i bezpieczeństwo

Dokumentuj różne metody uwierzytelniania w swoim API. W przypadku aplikacji wielodonajemczych (multi-tenant), właściwa dokumentacja uwierzytelniania jest kluczowa:

Autoryzacja tokenem Bearer

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.

Autoryzacja kluczem API

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description API key for authentication

Autoryzacja 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

Autoryzacja podstawowa (Basic Authentication)

// @securityDefinitions.basic BasicAuth

Stosuj zabezpieczenia do konkretnych punktów końcowych:

// @Security Bearer
// @Security ApiKeyAuth

Dostosowywanie interfejsu Swagger UI

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

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

// Z niestandardowym tytułem
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

Zautomatyzuj generowanie dokumentacji Swagger w swoim pipeline CI/CD:

# Przykład dla GitHub Actions
name: Generate Swagger Docs
on: [push]

jobs:
  swagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Install swag
        run: go install github.com/swaggo/swag/cmd/swag@latest
      
      - name: Generate Swagger docs
        run: swag init
      
      - name: Commit docs
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add docs/
          git commit -m "Update Swagger documentation" || exit 0
          git push

Najlepsze praktyki

1. Spójny styl adnotacji

Zachowuj spójne formatowanie dla wszystkich punktów końcowych:

// HandlerName godoc
// @Summary      Brief description (under 50 chars)
// @Description  Detailed description of what the endpoint does
// @Tags         resource-name
// @Accept       json
// @Produce      json
// @Param        name  location  type  required  "description"
// @Success      200   {object}  ResponseType
// @Failure      400   {object}  ErrorResponse
// @Router       /path [method]

2. Stosuj opisowe przykłady

Dodawaj realistyczne przykłady, aby pomóc konsumentom 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

Włącz wszystkie możliwe kody statusu HTTP:

// @Success      200  {object}  Product
// @Success      201  {object}  Product
// @Failure      400  {object}  ErrorResponse "Bad Request"
// @Failure      401  {object}  ErrorResponse "Unauthorized"
// @Failure      403  {object}  ErrorResponse "Forbidden"
// @Failure      404  {object}  ErrorResponse "Not Found"
// @Failure      422  {object}  ErrorResponse "Validation Error"
// @Failure      500  {object}  ErrorResponse "Internal Server Error"

4. Wersjonuj swoje API

Stosuj właściwe wersjonowanie w ścieżce bazowej:

// @BasePath  /api/v1

I organizuj 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 uporządkować punkty końcowe:

// @Tags products
// @Tags orders
// @Tags users

6. Utrzymuj dokumentację w aktualności

Uruchamiaj swag init przed każdym commitem lub zintegruj ten proces z etapem budowania:

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

Testowanie dokumentacji Swagger

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

func TestSwaggerGeneration(t *testing.T) {
    // Sprawdź, czy swagger.json istnieje
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("swagger.json not found, run '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)
}

Częste problemy i rozwiązania

Dokumentacja się nie aktualizuje

Jeśli zmiany nie pojawiają się w dokumentacji, upewnij się, że regenerujesz ją poprawnie:

swag init --parseDependency --parseInternal

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

Nierozpoznawane typy niestandardowe

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

type CustomTime struct {
    time.Time
}

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

Albo użyj tagu swaggertype:

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

Tablice i wyliczenia (Enums)

Dokumentuj typy tablicowe i wyliczenia:

type Filter struct {
    Status []string `json:"status" enums:"active,inactive,pending"`
    Tags   []string `json:"tags"`
}

// @Param  status  query  string  false  "Status filter" Enums(active, inactive, pending)

Alternatywne podejścia

Chociaż swaggo jest najpopularniejszym wyborem, istnieją inne opcje:

go-swagger

Bogatsze w funkcje, ale bardziej złożone rozwiązanie:

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

Ręczne pliki OpenAPI

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

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

Następnie serwuj je za pomocą:

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

Integracja z AI i LLM

Przy budowaniu API integrujących się z usługami AI, właściwa dokumentacja staje się kluczowa. Na przykład, przy pracy z strukturalizowanymi wyjściami LLM, Swagger pomaga w dokumentowaniu złożonych schematów żądań i odpowiedzi:

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"Summarize this text"`
    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      Generate structured LLM output
// @Description  Generate text with constrained output schema
// @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) {
    // Implementacja
}

Rozważania 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 uruchamiania: Dokumentacja jest ładowana raz przy starcie
  • Pamięć: Zwykle dodaje 1-2 MB do rozmiaru binarystyki
  • Czas odpowiedzi: Brak wpływu na same punkty końcowe API

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

  • Podział na wiele plików Swagger
  • Leniwe ładowanie zasobów interfejsu Swagger UI
  • Serwowanie dokumentacji z oddzielnej usługi

Rozważania dotyczące bezpieczeństwa

Przy udostępnianiu dokumentacji Swagger:

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

Podsumowanie

Dodanie dokumentacji Swagger do Twojego API w Go zmienia doświadczenie deweloperskie z zgadywania na sterowaną eksplorację. Biblioteka swaggo sprawia, że ten proces jest prosty, generując kompleksową dokumentację OpenAPI z adnotacji w Twoim kodzie.

Najważniejsze wnioski:

  • Zacznij od podstawowych adnotacji i rozwijaj je stopniowo
  • Utrzymuj synchronizację dokumentacji z kodem za pomocą CI/CD
  • Używaj Swagger UI do interaktywnego testowania podczas rozwoju
  • Dokładnie dokumentuj uwierzytelnianie, błędy i przypadki brzegowe
  • Rozważ implikacje bezpieczeństwa przy udostępnianiu dokumentacji

Niezależnie od tego, czy budujesz mikrousługi, publiczne API, czy narzędzia wewnętrzne, dokumentacja Swagger przynosi korzyści w postaci zmniejszenia obciążenia wsparcia, szybszego onboardingu i lepszego projektowania API. Początkowa inwestycja w naukę składni adnotacji szybko staje się rutyną, a automatyczne generowanie zapewnia, że Twoja dokumentacja nigdy nie pozostaje w tyle za implementacją.

Dla deweloperów Go połączenie mocnego typowania, generowania kodu i systemu adnotacji swaggo tworzy potężny przepływ pracy, który czyni dokumentację API naturalną częścią procesu developmentu, a nie czymś doklejanym na końcu.

Jeśli stos HTTP w dalszym ciągu pozostaje otwartym pytaniem, Frameworki Web w Go w 2026 roku: porównanie net/http, chi, Gin, Echo, Fiber benchmarkuje wszystkie pięć opcji na tym samym obciążeniu, więc decyzja o frameworku może zostać podjęta, zanim zostanie podłączony zestaw narzędzi do dokumentacji.

Przydatne linki

Zewnętrzne zasoby

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.