Go API に Swagger を追加する

コードの注釈から自動生成されたOpenAPIドキュメント

目次

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

swagger api specs on agile board この素晴らしい画像は、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 Type "Bearer" followed by a space and JWT token.

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

Ginフレームワークでの実装

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

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  独自の識別子で1つの製品を取得
// @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.jsonswagger.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")
}

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

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  "ページあたりのアイテム数" 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 読み書きアクセスを管理情報に付与

ベーシック認証

// @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  "説明"
// @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 --parseDependency --parseInternal

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

7. カスタムタイプが認識されない場合

外部パッケージのタイプに対してswaggertypeタグを使用します:

type CustomTime struct {
    time.Time
}

func (CustomTime) SwaggerDoc() map[string]string {
    return map[string]string{
        "time": "RFC3339タイムスタンプ",
    }
}

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

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

8. 配列と列挙型

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

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

// @Param  status  query  string  false  "ステータスフィルタ" Enums(active, inactive, pending)

代替アプローチ

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

go-swagger

より機能豊かですが複雑な代替手段:

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

手動でOpenAPIファイルを記述

完全な制御を求める場合、YAMLでOpenAPI仕様を手動で記述します:

openapi: 3.0.0
info:
  title: Product API
  version: 1.0.0
paths:
  /products:
    get:
      summary: 製品一覧
      responses:
        '200':
          description: 成功

その後、次のように提供します:

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

AIおよびLLMとの統合

AIサービスと統合するAPIを構築する際、適切なドキュメンテーションが非常に重要です。たとえば、構造化されたLLM出力を扱う際、Swaggerは複雑なリクエストおよびレスポンススキーマをドキュメント化します:

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"このテキストを要約してください"`
    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. 内部詳細の公開を絶対にしない
  • 内部エンドポイントをドキュメント化しない
  • データベーススキーマを直接公開しない
  • ドキュメンテーションのエラーメッセージを sanitize

結論

Go APIにSwaggerドキュメンテーションを追加することで、開発者体験は推測からガイド付きの探索へと変化します。swaggoライブラリは、コード注釈から包括的なOpenAPIドキュメンテーションを生成することで、このプロセスを簡単に行います。

重要なポイント:

  • 基本的な注釈から始め、段階的に拡張
  • CI/CDを通じてドキュメンテーションをコードと同期
  • 開発中にインタラクティブなテストのためにSwagger UIを使用
  • 認証、エラー、エッジケースを丁寧にドキュメント化
  • ドキュメンテーションを公開する際のセキュリティ上の考慮点を考慮

マイクロサービス、パブリックAPI、または内部ツールを構築する際、Swaggerドキュメンテーションはサポート負担の削減、オンボーディングの加速、およびより良いAPI設計に恩恵をもたらします。注釈構文の学習にかかる初期投資はすぐに日常的になり、自動生成によりドキュメンテーションが実装に後れを取ることはありません。

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

有用なリンク

外部リソース