Skip to content

swaggo で API ドキュメント作成

API 設計書の作成方法について説明します。swag によってコメントから swagger を自動生成するようにしています。

cf.

ライブラリインストール

下記コマンドで swag および http-swagger をインストールします:

go install github.com/swaggo/swag/cmd/swag@latest
go get -u github.com/swaggo/http-swagger

実装

cmd/main.go

アプリケーションの概要およびメタ情報を追記します。

  package main

  import (
      "easyapp/internal/handler/router"
      "fmt"
      "log"
      "net/http"
  )

+ // @title Easy App
+ // @version 1.0.0
+ // @description サンプルのREST APIです。
+ //
+ // @BasePath /api/v1
  func main() {

      fmt.Println("Server started at http://localhost:8080")
      log.Fatal(http.ListenAndServe(":8080", router.Routing()))
  }

router/base.go

swagger ページへのルーティングを設定します。

  package router

  import (
      "easyapp/internal/infrastructure"
      "net/http"

+     _ "easyapp/api"

+     httpSwagger "github.com/swaggo/http-swagger"
  )

  // routerのインターフェースです。
  type Router interface {

      // ルーティング情報をセットします。
      SetRouting(m *http.ServeMux)
  }

  // APIのベースURI
  const basePath string = "/api/v1"

  // ルーティングを設定します。
  func Routing() *http.ServeMux {

      // データベースに接続
      db := infrastructure.ConnectDB()

      // 各handlerに紐づくルーティングを設定
      m := http.NewServeMux()

+     // swagger UI のルーティング
+     m.Handle("/swagger/", httpSwagger.WrapHandler)

      // auth
      NewAuthRouter(db).SetRouting(m)

      return m
  }

handler/auth_handler.go

各 API のインターフェース仕様を記載します:

  package handler

  import (
      "easyapp/internal/apperrors"
      "easyapp/internal/handler/model"
      "easyapp/internal/usecase"
      "easyapp/internal/usecase/payload"
      "encoding/json"
      "net/http"
  )

  // 認証のハンドラインターフェースです。
  type AuthHandler interface {

      // 認証処理を呼び出します。
      Login(w http.ResponseWriter, r *http.Request)

      // ユーザ情報取得処理を呼び出します。
      Me(w http.ResponseWriter, r *http.Request)
  }

  type authHandler struct {
      authUsecase usecase.AuthUsecase
  }

  func NewAuthHandler(authUsecase usecase.AuthUsecase) AuthHandler {
      return &authHandler{authUsecase: authUsecase}
  }

+ // @Summary 認証
+ // @Description 認証処理を行います。リクエストに不備があった場合はエラーレスポンスを返します。
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param LoginReq body model.LoginReq true "認証向けのリクエストモデル"
+ // @Success 200 {object} model.LoginRes "正常に処理された場合"
+ // @Failure 422 {object} apperrors.sampleErrorRes "エラーが発生した場合"
+ // @Router /login [post]
  func (h *authHandler) Login(w http.ResponseWriter, r *http.Request) {

      req := model.NewLoginReq(r)

      out, err := h.authUsecase.Login(payload.NewLoginIn(req.Name, req.Password))
      if err != nil {
          apperrors.HandleError(w, err)
          return
      }

      res := model.NewLoginRes(out.Valid())
      w.Header().Set("Content-Type", "application/json")
      w.WriteHeader(http.StatusOK)
      json.NewEncoder(w).Encode(res)
  }

+ // @Summary ユーザ情報取得
+ // @Description ユーザ情報を取得します。
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param MeReq query model.MeReq false "ユーザ情報取得向けのリクエストモデル"
+ // @Success 200 {object} model.MeRes "正常に処理された場合"
+ // @Router /me [get]
  func (h *authHandler) Me(w http.ResponseWriter, r *http.Request) {

      req := model.NewMeReq(r)

      out := h.authUsecase.Me(payload.NewMeIn(req.Name))

      res := model.NewMeRes(out.Name(), out.Age())
      w.Header().Set("Content-Type", "application/json")
      w.WriteHeader(http.StatusOK)
      json.NewEncoder(w).Encode(res)
  }

model/auth_model.go

各モデルクラスの example 記載します:

  package model

  import (
      "encoding/json"
      "net/http"
  )

  // 認証向けのリクエストモデルです。
  type LoginReq struct {
-     Name     string `json:"name"`     // ユーザ名
-     Password string `json:"password"` // パスワード
+     Name     string `json:"name" example:"nob"`        // ユーザ名
+     Password string `json:"password" example:"passwd"` // パスワード
  }

  func NewLoginReq(r *http.Request) LoginReq {

      var req LoginReq
      decoder := json.NewDecoder(r.Body)
      if err := decoder.Decode(&req); err != nil {
          return *new(LoginReq)
      }
      return req
  }

  // 認証向けのレスポンスモデルです。
  type LoginRes struct {
-     Valid bool `json:"valid"` // 認証可否
+     Valid bool `json:"valid" example:"true"` // 認証可否
  }

  func NewLoginRes(valid bool) LoginRes {
      return LoginRes{Valid: valid}
  }

  // ユーザ情報取得向けのリクエストモデルです。
  type MeReq struct {
-     Name string `json:"name"` // ユーザ名
+     Name string `json:"name" example:"nob"` // ユーザ名
  }

  func NewMeReq(r *http.Request) MeReq {
      return MeReq{Name: r.URL.Query().Get("name")}
  }

  // ユーザ情報取得向けのレスポンスモデルです。
  type MeRes struct {
-     Name string `json:"name"` // ユーザ名
-     Age  int    `json:"age"`  // 年齢
+     Name string `json:"name" example:"nob"` // ユーザ名
+     Age  int    `json:"age" example:"13"`   // 年齢
  }

  func NewMeRes(name string, age int) MeRes {
      return MeRes{Name: name, Age: age}
  }

apperrors/sample_error.go

例外発生時レスポンスモデルの example を記載します:

  package apperrors

  import (
      "encoding/json"
      "net/http"
  )

  // サンプルのエラーです。
  type SampleError struct {
      message string // エラーメッセージ
  }

  func NewSampleError(message string) EasyappError {
      return &SampleError{message: message}
  }

  func (e *SampleError) Error() string {
      return e.message
  }

  func (e *SampleError) ReturnError(w http.ResponseWriter) {

      res := sampleErrorRes{Message: e.message}
      w.Header().Set("Content-Type", "application/json")
      w.WriteHeader(http.StatusUnprocessableEntity)
      json.NewEncoder(w).Encode(res)
  }

  // サンプルエラーのレスポンスモデルです。
  type sampleErrorRes struct {
-     Message string `json:"message"` // エラーメッセージ
+     Message string `json:"message" example:"エラーが発生しました。"` // エラーメッセージ
  }

動作確認

下記コマンドで swagger ドキュメントを生成します:

swag init -o ./api -g cmd/main.go

アプリを起動します:

go run cmd/main.go

アプリ起動後、http://localhost:8080/swagger/index.html で swagger ドキュメントを確認できます。