Dodawanie Swagger do swojej API w Go
Automatyczne generowanie dokumentacji OpenAPI na podstawie adnotacji w kodzie
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.
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 initzajmuje 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:
- Wyłącz w środowisku produkcyjnym (jeśli API jest wewnętrzne):
if os.Getenv("ENV") == "production" {
// Nie rejestruj punktu końcowego Swagger
return
}
- Dodaj autoryzację:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- Ogranicz przepustowość punktu końcowego:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- 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
- Go Cheatsheet
- Tworzenie REST API w Go
- Porównanie ORMów dla PostgreSQL w Go: GORM vs Ent vs Bun vs sqlc
- Wzorce baz danych dla aplikacji wieloudziałowych z przykładami w Go
- LLM z wyjściem strukturalnym: Ollama, Qwen3 i Python lub Go
- Wydajność AWS Lambda: JavaScript vs Python vs Go