GoのAPI開発が変わる!GinやChiに乗せるOpenAPI層「huma」の魅力
「GoでAPIサーバーを作るなら、どのフレームワークがいいんだろう?」
Go言語でWeb開発をしていると、必ず一度はぶつかるこの疑問。標準の net/http(Go 1.22からはルーティングも強化されましたね)、定番の Gin や Echo、シンプル派に人気の 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パスと関数の紐付け)を行いません。ルーティングは Gin、Chi、Echo、あるいは標準の net/http に任せます。humaは、そのルーターの「ハンドラー(コントローラー)」として機能し、以下の役割を担います。
- リクエストの型安全なパースと自動バリデーション
- レスポンスの型安全なシリアライズ
- 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の構造体タグ(
maxLengthやdoc)から直接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を試してみてください!
Discussion