Go APIにSwaggerを追加する

コードのアノテーションからOpenAPIドキュメントを自動生成します

目次

APIドキュメントは、あらゆるモダンアプリケーションにおいて重要です。Go API向けのSwagger(OpenAPI)は、業界標準となっています。 Goの開発者にとっては、swaggoがコードの注釈から包括的なAPIドキュメントを直接生成するための洗練されたソリューションを提供します。

アジャイルボード上のswagger api specs この美しい画像は、AIモデル Flux 1 devによって生成されたものです。

Go APIにおけるSwaggerの重要性

REST APIを構築する際、コードが進化するにつれてドキュメントはしばしば陳腐化します。Swaggerは、ソースコードからドキュメントを生成することによってこの問題を解決し、実装と同期を保つことを保証します。インタラクティブなSwagger UIを使用することで、開発者はブラウザからエンドポイントを直接テストでき、開発者体験が大幅に向上します。

マイクロサービスやパブリックAPIを構築するチームにとって、Swaggerドキュメントは以下の点で不可欠です:

  • クライアント生成: 複数の言語でクライアントライブラリを自動的に作成
  • 契約テスト: 定義されたスキーマに対してリクエストとレスポンスを検証
  • チームコラボレーション: API契約の単一の信頼できるソースを提供
  • 開発者のオンボーディング: 新しいチームメンバーがインタラクティブにAPIを探索可能

swaggoでの始め方

swaggoライブラリは、GoアプリケーションにSwaggerサポートを追加するための最も人気のあるツールです。これは、コード内の特別なコメントをパースして、OpenAPI 3.0仕様ファイルを生成することにより機能します。

インストール

まず、swag CLIツールをインストールしてください:

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

次に、使用しているフレームワークに対応するSwaggerミドルウェアパッケージを追加します。Ginの場合:

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

Echoの場合:

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

Fiberの場合:

go get -u github.com/gofiber/swagger

基本的な設定

main.goファイルに一般的なAPI情報を追加することから始めましょう。GoでのREST APIを構成する方法と同様に、注釈は明確かつ説明的にすべきです:

// @title           Product API
// @version         1.0
// @description     Swaggerドキュメント付きの製品管理API
// @termsOfService  http://swagger.io/terms/

// @contact.name   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 "Bearer"をタイプしてスペース、そしてJWTトークンを続けます。

func main() {
    // あなたのアプリケーションコード
}

Ginフレームワークを用いた実装

Ginを使用して完全な例を実装してみましょう。まず、structタグ付きのデータモデルを定義します:

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"`
}

次に、ハンドラー関数に注釈を付けます。データベース操作を行う際、これらの注釈はデータフローのドキュメント化に役立ちます:

// GetProduct godoc
// @Summary      IDで製品を取得
// @Description  一意の識別子によって単一の製品を取得
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "製品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")
    // ここに実装
    c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

// CreateProduct godoc
// @Summary      新しい製品を作成
// @Description  カタログに新しい製品を追加
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "製品オブジェクト"
// @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
    }
    // データベースに保存
    c.JSON(201, product)
}

ドキュメントの生成

コードに注釈を付けた後、Swaggerドキュメントを生成します:

swag init

これにより、docsフォルダ内にswagger.json、swagger.yaml、およびGoファイルが作成されます。生成されたドキュメントをインポートし、Swaggerエンドポイントを登録します:

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    
    _ "yourproject/docs" // 生成されたドキュメントをインポート
)

func main() {
    r := gin.Default()
    
    // APIルート
    v1 := r.Group("/api/v1")
    {
        v1.GET("/products/:id", GetProduct)
        v1.POST("/products", CreateProduct)
    }
    
    // Swaggerエンドポイント
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    
    r.Run(":8080")
}

これで、http://localhost:8080/swagger/index.htmlでインタラクティブなAPIドキュメントにアクセスできます。

Echoフレームワークを用いた実装

Echoのユーザーは、Echo固有のミドルウェアを使用するが、類似のパターンに従います:

package main

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

func main() {
    e := echo.New()
    
    // APIルート
    api := e.Group("/api/v1")
    api.GET("/products/:id", getProduct)
    api.POST("/products", createProduct)
    
    // Swaggerエンドポイント
    e.GET("/swagger/*", echoSwagger.WrapHandler)
    
    e.Start(":8080")
}

Fiberフレームワークを用いた実装

Fiberの実装も同様に簡単です:

package main

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

func main() {
    app := fiber.New()
    
    // APIルート
    api := app.Group("/api/v1")
    api.Get("/products/:id", getProduct)
    api.Post("/products", createProduct)
    
    // Swaggerエンドポイント
    app.Get("/swagger/*", swagger.HandlerDefault)
    
    app.Listen(":8080")
}

高度なSwagger注釈

複雑なリクエストボディのドキュメント化

ネストされた構造体や配列の場合:

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      新しい注文を作成
// @Description  複数のアイテムと発送情報を含む注文を作成
// @Tags         orders
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "注文の詳細"
// @Success      201    {object}  Order
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // 実装
}

ファイルアップロードのドキュメント化

// UploadImage godoc
// @Summary      製品画像をアップロード
// @Description  製品用の画像ファイルをアップロード
// @Tags         products
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "製品ID"
// @Param        file  formData  file  true  "画像ファイル"
// @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")
    // アップロードの処理
}

クエリパラメータとページネーション

// ListProducts godoc
// @Summary      ページネーション付きで製品をリスト表示
// @Description  オプションのフィルタリング付きのページネーションされた製品リストを取得
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        page      query     int     false  "ページ番号" default(1)
// @Param        page_size query     int     false  "1ページあたりのアイテム数" default(10)
// @Param        category  query     string  false  "カテゴリでフィルタリング"
// @Param        min_price query     number  false  "最低価格"
// @Param        max_price query     number  false  "最高価格"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // ページネーション付きの実装
}

認証とセキュリティ

API内の異なる認証方法をドキュメント化します。マルチテナントアプリケーションの場合、適切な認証ドキュメントは重要です:

Bearerトークン認証

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description "Bearer"をタイプしてスペース、そしてJWTトークンを続けます。

APIキー認証

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description 認証用のAPIキー

OAuth2認証

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write 書き込みアクセスを付与
// @scope.admin 管理情報への読み取り/書き込みアクセスを付与

Basic認証

// @securityDefinitions.basic BasicAuth

特定のエンドポイントにセキュリティを適用します:

// @Security Bearer
// @Security ApiKeyAuth

Swagger UIのカスタマイズ

Swagger UIの外観と動作をカスタマイズできます:

// カスタム設定
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))

// カスタムタイトル付き
r.GET("/swagger/*any", ginSwagger.WrapHandler(
    swaggerFiles.Handler,
    ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
    ginSwagger.DefaultModelsExpandDepth(-1),
))

本番環境でSwaggerを無効にするには:

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

CI/CDとの統合

CI/CDパイプラインでのSwaggerドキュメントの生成を自動化します:

# 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

ベストプラクティス

1. 一貫した注釈スタイルの維持

すべてのエンドポイントで一貫したフォーマットを維持してください:

// HandlerName godoc
// @Summary      短い説明 (50文字以下)
// @Description  エンドポイントが何をするかの詳細な説明
// @Tags         リソース名
// @Accept       json
// @Produce      json
// @Param        name  location  type  required  "description"
// @Success      200   {object}  ResponseType
// @Failure      400   {object}  ErrorResponse
// @Router       /path [method]

2. 説明的な例の使用

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. すべてのレスポンスコードのドキュメント化

考えられるすべての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. APIのバージョン管理

ベースパスに適切なバージョン管理を使用します:

// @BasePath  /api/v1

そして、コードをそれに応じて構成します:

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

5. 関連するエンドポイントのグループ化

タグを使用して、エンドポイントを論理的に整理します:

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

6. ドキュメントの更新を維持する

すべてのコミット前にswag initを実行するか、ビルドプロセスに統合します:

#!/bin/bash
# pre-commit フック
swag init
git add docs/

Swaggerドキュメントのテスト

AWS Lambdaなどのサーバーレスアーキテクチャで作業する際、APIドキュメントのテストはさらに重要になります:

func TestSwaggerGeneration(t *testing.T) {
    // swagger.jsonが存在することを確認
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("swagger.json not found, run 'swag init'")
    }
    
    // swaggerエンドポイントが応答することを確認
    r := setupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

よくある問題と解決策

ドキュメントが更新されない

変更が反映されない場合、ドキュメントを再生成していることを確認してください:

swag init --parseDependency --parseInternal

--parseDependencyフラグは外部依存関係をパースし、--parseInternalは内部パッケージをパースします。

カスタム型が認識されない

外部パッケージからの型には、swaggertypeタグを使用します:

type CustomTime struct {
    time.Time
}

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

または、swaggertypeタグを使用します:

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

配列と列挙型 (Enum)

配列型と列挙型をドキュメント化します:

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)

代替アプローチ

swaggoが最も人気のある選択肢ですが、他の選択肢も存在します:

go-swagger

より機能が豊富だが複雑な代替案:

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

手動によるOpenAPIファイル

完全なコントロールが必要な場合、OpenAPI仕様を手動でYAMLで記述します:

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

そして、以下のように配信します:

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

AIとLLMとの統合

AIサービスと統合するAPIを構築する際、適切なドキュメントは不可欠です。例えば、構造化LLM出力に取り組む際、Swaggerは複雑なリクエストとレスポンスのスキーマのドキュメント化に役立ちます:

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      構造化LLM出力を生成
// @Description  制約付き出力スキーマでテキストを生成
// @Tags         llm
// @Accept       json
// @Produce      json
// @Param        request  body      LLMRequest  true  "LLMパラメータ"
// @Success      200      {object}  map[string]interface{}
// @Failure      400      {object}  ErrorResponse
// @Router       /llm/generate [post]
func GenerateStructured(c *gin.Context) {
    // 実装
}

パフォーマンス上の考慮事項

Swaggerドキュメントはパフォーマンスに最小限の影響しか及ぼしません:

  • ビルド時間: ほとんどのプロジェクトでswag initは1〜3秒かかります
  • 実行時: ドキュメントは起動時に一度だけ読み込まれます
  • メモリ: 通常、バイナリサイズに1〜2MB追加されます
  • レスポンスタイム: APIエンドポイント自体には影響しません

非常に大きなAPI(100以上のエンドポイント)の場合、以下を検討してください:

  • 複数のSwaggerファイルに分割する
  • Swagger UIアセットの遅延読み込み
  • ドキュメントを別サービスから配信する

セキュリティ上の考慮事項

Swaggerドキュメントを公開する際:

  1. 本番環境で無効にする(APIが内部向けの場合):
if os.Getenv("ENV") == "production" {
    // Swaggerエンドポイントを登録しない
    return
}
  1. 認証を追加する:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. エンドポイントにレート制限をかける:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. 内部詳細を絶対に公開しない:
  • 内部エンドポイントをドキュメント化しない
  • データベーススキーマを直接公開しない
  • ドキュメント内のエラーメッセージをサニタイズする

結論

Go APIにSwaggerドキュメントを追加することは、開発者体験を推測から導かれた探索に変革します。swaggoライブラリは、コードの注釈から包括的なOpenAPIドキュメントを生成することにより、このプロセスを簡単に行います。

主要なポイント:

  • 基本的な注釈から始まり、徐々に拡張していく
  • CI/CDを通じてドキュメントとコードの同期を保つ
  • 開発中のインタラクティブなテストにSwagger UIを使用する
  • 認証、エラー、エッジケースを徹底的にドキュメント化する
  • ドキュメントを公開する際、セキュリティ上の影響を考慮する

マイクロサービス、パブリックAPI、または内部ツールの構築に際して、Swaggerドキュメントはサポート負荷の削減、より迅速なオンボーディング、より優れたAPI設計において成果をもたらします。注釈構文を学習するための初期投資はすぐに習慣となり、自動生成により、ドキュメントが実装に遅れを取ることがなくなります。

Goの開発者にとって、強い型付け、コード生成、swaggoの注釈システムの組み合わせは、APIドキュメントを後回しにすることではなく、開発プロセスの自然な一部とする強力なワークフローを創出します。

もし基盤となるHTTPスタックがまだ不明確な場合、2026年のGo Webフレームワーク比較: net/http, chi, Gin, Echo, Fiberでは、すべての5つの選択肢を同じワークロードでベンチマークしているため、ドキュメントツールを導入する前にフレームワークの決定をすることができます。

有用的なリンク

外部リソース

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。