🕺

GoのAPI開発が変わる!GinやChiに乗せるOpenAPI層「huma」の魅力

に公開

「GoでAPIサーバーを作るなら、どのフレームワークがいいんだろう?」

Go言語でWeb開発をしていると、必ず一度はぶつかるこの疑問。標準の net/http(Go 1.22からはルーティングも強化されましたね)、定番の GinEcho、シンプル派に人気の Chi など、選択肢はたくさんあります。

しかし最近、海外のGoコミュニティでじわじわと注目を集めているフレームワークがあるのをご存知でしょうか?それが今回紹介する huma(ヒューマ)です。

「また新しいフレームワーク?もうお腹いっぱいだよ…」と思うかもしれません。しかし、humaは「GinやChiの代わり」になるものではありません。

humaの本当の立ち位置は、「GinやChiなどの既存ルーターの上に乗せて、型安全なリクエスト処理とOpenAPIドキュメントの自動生成を提供する層」 なのです。

本記事では、humaの「本当の使用用途」を明確にし、よく使われる gin-swagger との比較、そして気になる GinやChiとのパフォーマンス比較(Ubuntu上での実測ベンチマーク) まで、たっぷりとお届けします!


humaの立ち位置:「ルーター」ではなく「OpenAPI層」

huma(発音: /'hjuːmɑ/)は、Go言語向けのモダンでシンプル、かつ高速・柔軟なHTTP REST/RPC API構築用マイクロフレームワークです。

公式サイト(huma.rocks)では、その目標について以下のように語られています。

A modern REST or HTTP RPC API backend framework for Go developers
Described by OpenAPI 3.1 & JSON Schema
Incremental adoption for teams with existing services
Bring your own router, middleware, and logging/metrics

ここで最も重要なのが 「Bring your own router(自分の好きなルーターを持ち込める)」 という点です。

huma自体はHTTPリクエストのルーティング(URLパスと関数の紐付け)を行いません。ルーティングは GinChiEcho、あるいは標準の net/http に任せます。humaは、そのルーターの「ハンドラー(コントローラー)」として機能し、以下の役割を担います。

  1. リクエストの型安全なパースと自動バリデーション
  2. レスポンスの型安全なシリアライズ
  3. Goの構造体(コード)からのOpenAPI 3.1仕様書とJSON Schemaの完全自動生成

つまり、humaは 「HTTPパッケージのようなもの」ではなく、「GinやChiをFastAPIのように進化させるための拡張パーツ」 と捉えるのが正解です。


gin-swagger と huma の違い

「OpenAPI(Swagger)を自動生成するなら、gin-swagger(swaggo)を使えばいいのでは?」と思う方も多いでしょう。

確かに gin-swagger は便利ですが、「コメントベース」 であるという大きな課題があります。

gin-swagger のアプローチ(コメントベース)

gin-swagger では、ハンドラー関数の上に特殊な形式のコメントを記述し、swag init コマンドを実行してドキュメントを生成します。

// @Summary ping example
// @Description do ping
// @Tags example
// @Accept json
// @Produce json
// @Success 200 {string} Helloworld
// @Router /example/helloworld [get]
func Helloworld(g *gin.Context) {
    g.JSON(http.StatusOK, "helloworld")
}

この方式の辛いところは以下の点です。

  • コードとドキュメントの乖離: コメントと実際の実装(構造体や処理)が分離しているため、実装を変更したのにコメントを修正し忘れると、嘘のドキュメントが生成されてしまいます。
  • 二重管理: リクエストのバリデーションルール(例: binding:"required")と、Swaggerのコメント(例: // @Param name query string true "Name")を別々に書く必要があります。
  • CLIの実行が必要: コードを変更するたびに swag init を実行し直す必要があります。

huma のアプローチ(コードベース・宣言的)

一方、humaは 「コード(Goの構造体と型)そのものがドキュメントの正となる」 アプローチをとります。Pythonの FastAPI と全く同じ思想です。

// 入力モデル(リクエスト)
type GreetingInput struct {
    // pathタグでパスパラメータを指定。maxLengthでバリデーションも兼ねる
    Name string `path:"name" maxLength:"30" doc:"Name to greet"`
}

// 出力モデル(レスポンス)
type GreetingOutput struct {
    Body struct {
        Message string `json:"message" example:"Hello, world!"`
    }
}

// オペレーションの登録
huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) {
    resp := &GreetingOutput{}
    resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
    return resp, nil
})

humaを使うと何が嬉しいのか?

  • 絶対に嘘をつかないドキュメント: Goの構造体タグ(maxLengthdoc)から直接OpenAPI仕様書が生成されるため、実装とドキュメントが100%一致します。
  • バリデーションの自動化: 構造体タグに書いた制約(maxLength:"30" など)に基づいて、humaが自動的にリクエストをバリデーションし、違反があればRFC9457準拠の美しいエラーJSONを返してくれます。
  • CLI不要: サーバーを起動するだけで、メモリ上で動的にOpenAPI仕様書が生成され、/openapi.json/docs(美しいUI)として配信されます。

「gin-swaggerを使わなくてもSwagger(OpenAPI)がいける」どころか、「gin-swaggerの辛い部分をすべて解決してくれる」のがhumaなのです。


インストールと環境構築

huma v2はジェネリクスを多用しているため、Go 1.25以降 が必要です。

# プロジェクトの初期化
mkdir my-huma-app && cd my-huma-app
go mod init my-huma-app

# humaのインストール
go get -u github.com/danielgtaylor/huma/v2

# ベースとなるルーターのインストール(例としてChiを使用)
go get -u github.com/go-chi/chi/v5

基本的な使い方(Chi + huma の例)

humaは様々なルーターに対応する「アダプター」を提供しています。ここではシンプルで標準的な Chi をベースにした例を紹介します。

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/danielgtaylor/huma/v2"
    "github.com/danielgtaylor/huma/v2/adapters/humachi"
    "github.com/go-chi/chi/v5"
)

// 1. リクエストの構造体を定義
type GreetingInput struct {
    Name string `path:"name" maxLength:"30" example:"world" doc:"Name to greet"`
}

// 2. レスポンスの構造体を定義
type GreetingOutput struct {
    Body struct {
        Message string `json:"message" example:"Hello, world!" doc:"Greeting message"`
    }
}

func main() {
    // 3. ベースとなるChiルーターを作成
    router := chi.NewMux()

    // 4. huma APIインスタンスを作成し、Chiルーターをラップする
    // ここでAPIのタイトルやバージョンを設定します
    api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))

    // 5. エンドポイント(オペレーション)を登録
    huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) {
        resp := &GreetingOutput{}
        resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
        return resp, nil
    })

    // 6. サーバーの起動(起動するのはChiルーター)
    fmt.Println("Server running on http://localhost:8888")
    http.ListenAndServe(":8888", router)
}

このコードを go run main.go で起動するだけで、以下がすべて自動で手に入ります。

  • GET /greeting/world へのルーティングとJSONレスポンス
  • name パラメータが30文字を超えた場合の自動エラー返却(422 Unprocessable Entity)
  • GET /openapi.json でのOpenAPI 3.1仕様書(JSON形式)の配信
  • GET /docs でのブラウザ向けAPIドキュメント画面(Stoplight Elements)の配信

既存のGinプロジェクトへの導入

すでにGinで動いているプロジェクトにも、humaを「部分的に」導入することができます。

import (
    "github.com/danielgtaylor/huma/v2"
    "github.com/danielgtaylor/huma/v2/adapters/humagin"
    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    
    // 既存のGinハンドラー(そのまま動く)
    engine.GET("/legacy", func(c *gin.Context) {
        c.JSON(200, gin.H{"status": "ok"})
    })

    // humaをGinにアタッチ
    api := humagin.New(engine, huma.DefaultConfig("My API", "1.0.0"))

    // 新しいエンドポイントはhumaで型安全に定義
    huma.Get(api, "/modern", func(ctx context.Context, input *struct{}) (*MyOutput, error) {
        // ...
    })

    engine.Run(":8888")
}

このように、「既存のルーティングやミドルウェア資産を活かしつつ、APIの入力・出力部分だけをhumaに置き換えていく」 という段階的な移行が可能です。


コマンドと操作方法まとめ

サーバー起動と確認

# サーバーの起動
go run main.go

# APIを呼び出す
curl http://localhost:8888/greeting/world

# バリデーションエラーを確認する(30文字超の名前を送る)
curl "http://localhost:8888/greeting/$(python3 -c "print('a'*31)")"
# → 422 Unprocessable Entity の美しいJSONエラーが返ってくる

自動生成されるエンドポイント

humaを起動すると、以下のエンドポイントが自動的に利用可能になります。

エンドポイント 説明
GET /openapi.json OpenAPI 3.1仕様書(JSON形式)
GET /openapi.yaml OpenAPI 3.1仕様書(YAML形式)
GET /openapi-3.0.json OpenAPI 3.0.3仕様書(JSON形式、旧ツール向け互換)
GET /docs Stoplight Elementsによるブラウザ向けAPIドキュメントUI
GET /schemas/{Name}.json 各モデルのJSON Schema

ブラウザで http://localhost:8888/docs にアクセスすると、Swagger UIよりもモダンで美しいAPIドキュメント画面がすぐに確認できます。


設定項目まとめ

リクエストパラメータのタグ

ハンドラーの入力構造体で使用できるタグの一覧です。これらを構造体に付与するだけで、値の取得とバリデーション、そしてドキュメント生成が同時に行われます。

パラメータ取得元タグ

タグ 説明
path URLパスパラメータ path:"user-id"
query クエリ文字列パラメータ query:"search"
header HTTPヘッダー header:"Authorization"
cookie Cookie cookie:"session_id"

バリデーション・スキーマタグ

タグ 対象型 説明
minimum 数値 最小値(以上) minimum:"0"
maximum 数値 最大値(以下) maximum:"100"
minLength 文字列 最小文字数 minLength:"1"
maxLength 文字列 最大文字数 maxLength:"255"
pattern 文字列 正規表現パターン pattern:"^[a-z]+$"
format 文字列 フォーマット(email, uri, date-time等) format:"email"
enum 任意 許可値のリスト enum:"a,b,c"
default 任意 デフォルト値 default:"hello"
doc 任意 フィールドの説明(OpenAPIに反映) doc:"User's name"
example 任意 サンプル値(OpenAPIに反映) example:"John"

huma.Config のカスタマイズ

huma.DefaultConfig() で生成される設定オブジェクトを書き換えることで、APIのメタデータやドキュメントのURLを変更できます。

config := huma.DefaultConfig("My API", "1.0.0")

// ドキュメントURLのカスタマイズ
config.DocsPath = "/api-docs"
config.OpenAPIPath = "/api-spec"

// 未知のクエリパラメータを拒否する(厳格なAPIにする場合)
config.RejectUnknownQueryParameters = true

// セキュリティスキームの設定(JWT Bearer認証の例)
config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
    "bearer": {
        Type:         "http",
        Scheme:       "bearer",
        BearerFormat: "JWT",
    },
}

api := humachi.New(router, config)

ベンチマーク比較:humaを被せると重くなる?

「リフレクションを使って構造体を解析しているなら、パフォーマンスが落ちるんじゃないの?」

Goエンジニアなら当然の疑問です。そこで、実際のUbuntu環境にて、以下の5パターンでベンチマークを計測してみました。

計測環境

  • OS: Ubuntu 22.04 (linux/amd64)
  • CPU: Intel(R) Xeon(R) Processor @ 2.50GHz(6コア)
  • Go: 1.24.1
  • 計測コマンド: go test -bench=. -benchmem -benchtime=5s -count=1

計測コードの概要

すべてのフレームワークで GET /greeting/{name} というエンドポイントを作成し、JSONで {"message": "Hello, {name}!"} を返す単純な処理を httptest を用いてインメモリで計測しました。

計測結果

フレームワーク 実行回数/5s 処理時間 (ns/op) メモリ (B/op) allocs/op
net/http (標準) 3,023,290 2,083 1,456 16
Gin v1.x 2,577,178 2,164 1,488 17
Chi v5.x 2,219,017 3,018 2,145 19
huma + Gin 1,859,868 3,130 1,257 18
huma + Chi 1,601,919 3,696 1,986 22

考察

処理速度について

標準の net/http(2,083 ns/op)や Gin(2,164 ns/op)が最速です。humaを被せると、huma + Gin で3,130 ns/op、huma + Chi で3,696 ns/opとなり、それぞれ約1,000〜1,600 ns(1〜1.6マイクロ秒)のオーバーヘッドが発生しています。

メモリ効率について

興味深いのはメモリ割り当て量です。huma + Gin のメモリ割り当て量(1,257 B/op)は、Gin単体(1,488 B/op)や標準ライブラリ(1,456 B/op)よりも少なくなっています。これはhuma内部でのバッファプールやアロケーション最適化が非常に優秀であることを示しています。

実用上の影響

1リクエストあたり約1〜1.6マイクロ秒のオーバーヘッドは、データベースアクセス(通常1〜100ミリ秒)や外部API呼び出しが発生する一般的なWebアプリケーションにおいては完全に誤差の範囲です。

このわずかなオーバーヘッドと引き換えに、「完璧なOpenAPIドキュメント」と「型安全なバリデーション」が手に入ることを考えると、費用対効果は圧倒的に高いと言えます。


まとめ

Go言語の次世代フレームワーク「huma」について、その本当の立ち位置と魅力を紹介しました。

humaのポイント

  • ルーターではなく、GinやChiの上に乗せる「OpenAPI・型安全層」である
  • gin-swagger のようなコメントベースの二重管理から解放される
  • コード(構造体)を書くだけで、バリデーションとOpenAPIドキュメント生成が全自動で行われる
  • パフォーマンスのオーバーヘッドは実用上無視できるレベルであり、メモリ効率はむしろ良い

「API仕様書を書くのが面倒くさい」「実装とドキュメントがいつもズレてしまう」と悩んでいるGoエンジニアの方は、ぜひ次のプロジェクトで、お気に入りのルーターと一緒にhumaを試してみてください!


参考リンク

VeriCerts Tech Blog

Discussion