2026年のGo Webフレームワーク比較:net/http、chi、Gin、Echo、Fiber
5つのGo HTTPスタック、1つのベンチマーク環境、実測数値。
Goの標準ライブラリには本番環境で利用できるHTTPサーバーが含まれており、Go 1.22以降は組み込みルーターがメソッドマッチングとパスパラメータを独自に処理します。フレームワークに関する問いは消え去ったわけではなく、むしろより狭い範囲に絞られています。
2026年のGoバックエンドにおけるほぼすべての意思決定をカバーするスタックは5つあります。標準ライブラリのnet/http、chi、Gin、Echo、そしてFiberです。これらは同じトレードオフ、すなわち「ライブラリが提供するもの」と「依存関係、規約、エコシステム互換性において自分が負担するもの」に対して、異なる立場を取っています。この記事では、ルーティングの力、ハンドラーの使いやすさ、ミドルウェア、および測定されたパフォーマンスの観点からこれらを比較し、単一の結論ではなく意思決定テーブルで締めくくります。
ゼロから始まり、選択のまわりの完全な構築プロセス——バリデーション、認証、テスト、デプロイ——を求めている場合は、ステップバイステップの解説がGoでのREST API構築にあります。ここでは焦点はスタック自体に留めます。

Go 1.22以降のServeMuxのリセット
フレームワーク論争の形が変わった理由は、標準ライブラリへの一つの追加にあります。Go 1.22はhttp.ServeMuxにメソッド認識パターンとパスパラメータを提供しました:
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
})
mux.HandleFunc("POST /users", createUser)
これ以前は、GET /users/{id}のようなAPIに第3者のルーターが必要であり、それがGin、chi、Echoがデフォルトになる原因でした。2026年現在、標準ライブラリは静的ルート、メソッドマッチング、単一セグメントおよびワイルドカードパラメータ、そして単純な関数ラップによるミドルウェアをカバーしています。依然として提供していないもの:ルートグループ、構成的なミドルウェアチェーン、ストラクチャ束縛(バインディング)、ルーティング単位のパラメータ型です。
競合者たち
| net/http (標準) | chi v5.3.2 | Gin v1.12.0 | Echo v4.16.0 | Fiber v2.52.15 | |
|---|---|---|---|---|---|
| HTTPエンジン | net/http | net/http | net/http | net/http | fasthttp |
| ハンドラーシグネチャ | func(w, r) |
func(w, r) |
func(c *gin.Context) |
func(c echo.Context) error |
func(c *fiber.Ctx) error |
| http.Handler互換 | はい | はい | はい | はい | いいえ |
| ミドルウェアエコシステム | 成長中、標準ライブラリ型 | 標準ライブラリ型 | 最大、Gin専用 | 大規模、Echo専用 | 成長中、fasthttp専用 |
| バインディング/バリデーション | 手動 | 手動 | ビルトインタグ | ビルトイン | ヘルパー経由 |
| HTTP/2 | はい | はい | はい | はい | いいえ |
| 依存関係 | 0 | 1 | 〜6 | 〜8 | 〜4 |
5つすべてのスタックは、パスパラメータとグループ化されたルートを持つトライーまたはラディックス形式のルーターを使用しています。構造的な分岐はハンドラーシグネチャの物語の最終行にあります。chiと標準ライブラリはプレーンのhttp.Handlerを話し、Gin、Echo、Fiberはリクエストを独自のコンテキスト型でラップします。Fiberは再び外れ値であり、net/httpではなくfasthttpの上に構築されているためです。
ベンチマーク対象
合成のルーターベンチマークを引用するのではなく、同じサービスをすべてのスタック上で実行しました。構成は以下の通りです:
- Go 1.27.1, Gin v1.12.0, chi v5.3.2, Echo v4.16.0, Fiber v2.52.15
- Intel Core Ultra 7 265, Linux, 20スレッド, localhost, TLSなし
- スタックごとに3つのルート:
GET /health,GET /users/{id},GET /api/v1/users/{uid}/orders/{oid} - ロード: ウォームアップパスの後に、2パラメータのルートに対して8つのkeep-aliveワーカーで3秒間
- すべてのハンドラーは
encoding/jsonを使用してリクエストごとに小さなJSONドキュメントをマーシャルします——したがって、数値にはルーティングだけでなくシリアライゼーションも含まれます
ホットルートの標準ライブラリ版:
mux.HandleFunc("GET /api/v1/users/{uid}/orders/{oid}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write(orderBody(r.PathValue("uid"), r.PathValue("oid")))
})
およびFiberの同等な実装:
app.Get("/api/v1/users/:uid/orders/:oid", func(c *fiber.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Send(orderBody(c.Params("uid"), c.Params("oid")))
})
数値の前に2つの誠実な注意があります。第一に、これはlocalhostでのマイクロワークロードです:データベースなし、TLSハンドシェイクなし、ミドルウェアチェーンなし。実際のサービスはI/Oでボトルネックになり、PostgreSQLをGORM, Ent, Bun, sqlc経由でクエリするリクエストにおけるフレームワークの割合は丸め誤差のレベルです。第二に、各スタックには独自のサーバープロセスと同一のハンドラー形状が割り当てられたため、比較はスタック間のものであり、手動調整された構成間のものではありません。
結果
動的な2パラメータルートにおけるスループット:
| スタック | リクエスト/秒 | 平均レイテンシ |
|---|---|---|
| Fiber (fasthttp) | 205,958 | 0.034 ms |
| Echo | 169,826 | 0.042 ms |
| Gin | 169,715 | 0.042 ms |
| net/http ServeMux | 169,708 | 0.042 ms |
| chi | 163,017 | 0.044 ms |
4つのnet/httpベースのスタックは互いの4%以内にあります。Fiberのfasthttpエンジンはこのlocalhostワークロードで約21%リードしています——実際に存在する差ですが、本番環境では単一のデータベース往復(0.5〜5 ms)で無関係になるような差です。
リクエストあたりのコストが実際どこにあるかを見るため、ネットワークなしでハンドラー呼び出しをベンチマークし、各ルーターをhttp.Handler.ServeHTTP経由(Fiberはfasthttpハンドラー経由)で直接呼び出しました:
| スタック | 時間/操作 | バイト/操作 | 割り当て/操作 |
|---|---|---|---|
| Gin | 289 ns | 192 B | 3 |
| Echo | 333 ns | 192 B | 3 |
| net/http ServeMux | 430 ns | 240 B | 5 |
| Fiber | 541 ns* | 192 B | 3 |
| chi | 592 ns | 897 B | 7 |
* Fiberの数値にはハーネスのオーバーヘッドが含まれています——テストは各イテレーションでリクエストをfasthttpの再利用可能なコンテキストにコピーする必要があり、実際のサーバーはワイヤーから読み取ることでこれを回避しています。上限値として扱い、上記のサーバーレベルのスループットがFiberの公平な比較です。
アロケーションプロファイルはより持続可能な物語です。GinとEchoはリクエストコンテキストをプールしているため、それらを通過するリクエストのコストは3回の割り当てです。標準ライブラリの5回の割り当ては、リクエストスコープのパイプリング自体に向いています。chiはルートパラメータをリクエストのcontext.Contextに保存するため、リクエストごとに897 Bと7回の割り当てとして現れます——豊富なルート情報を持ちながらプレーンのhttp.Handlerのままいるための代償です。
Fiberとfasthttpのトレードオフ
Fiberはサーバーベンチマークで最も速いスタックであり、その速度には互換性の代償が伴います。fasthttpはゼロから書かれたHTTP実装であり、独自のリクエスト/レスポンス型を持つため:
net/httpミドルウェアは取り付けられません。http.Handlerまたは*http.Requestを期待するライブラリは、変換またはFiberネイティブの移植が必要です。- HTTP/2なし。 fasthttpは実装していません。Goの
net/httpはTLS経由で標準ライブラリから無料のh2を得られます。 - WebSocket、ストリーミング、および
http.Flusherのようなトリックはすべてfasthttpのパスを取り、これはほとんどのGoドキュメントが想定する標準ライブラリの動作と異なります。
Fiberが引き換えに得るもの:上記の表の中で最も速い数値、Express風なAPI、そしてワイヤーパースの優位性を含むとフレームワークベンチが示唆するよりも低い割り当て。レスポンスが小さく、ルーティングが支配的であり、HTTP/2がアップストリームで終了し、必要なミドルウェアがFiberエコシステムに存在する場合に適しています。これらの条件は一部のエッジサービスに当てはまり、それ以外にはほとんど当てはまりません。
chi: 退屈な中道
chiはルーターとミドルウェアパイプラインで、それ以上のものはありません。ハンドラーはfunc(w, r)で、ミドルウェアはfunc(http.Handler) http.Handlerであり、標準ライブラリのために書かれたものはアダプタなしで取り付けられます:
r := chi.NewRouter()
r.Use(RequestID, RealIP, Logger, Recoverer)
r.Route("/api/v1", func(r chi.Router) {
r.Get("/users/{uid}/orders/{oid}", getOrder)
})
これが全体的な主張です:標準インターフェースの上のルートグループとミドルウェアスタック。割り当てテーブルはそれを誠実に価格づけています——標準ライブラリ単体よりもリクエストごとの豊富なルートコンテキスト、引き換えに構成的なミドルウェアとネストされたルートを。フレームワークアイデンティティなしで構造を求めているサービスに対して、chiとnet/httpの組み合わせは、多くのGoチームが収束する構成です。
Gin vs Echo、コードで
GinとEchoはパフォーマンス上で近親であり——両方のベンチマークでノイズレベル内——したがって、それら間の選択はAPIの好みとエラーハンドリングの哲学です。
EchoはエラーをHTTPErrorHandlerに集中させます。ハンドラーはerrorを返し、一つの関数がエラーをレスポンスにマッピングします:
func getOrder(c echo.Context) error {
o, err := store.Order(c.Param("uid"), c.Param("oid"))
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, "order not found")
}
return c.JSON(http.StatusOK, o)
}
Ginのハンドラーは何も返しません。エラーはコンテキストに蓄積され、選択した場所でレンダリングされます:
func getOrder(c *gin.Context) {
o, err := store.Order(c.Param("uid"), c.Param("oid"))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "order not found"})
return
}
c.JSON(http.StatusOK, o)
}
Echoのモデルは、ラップされたエラーと集中型の境界処理を持つGoのエラーハンドリングアーキテクチャとクリアにマッピングされ——errors.Isの結果をステータスコードに変換する場所を一つにします。Ginのモデルは、ハンドラーごとに決定するより一般的なパターンに一致します。エラーを超えると、Ginの決定論的優位性はエコシステムのサイズとバリデーションタグ付きのビルトインストラクチャバインディングです。Echoのものは、一貫性のあるコンテキストAPIとより強力なビルトインミドルウェアです。どちらも独自のアダプタを介してSwaggerジェネレーションを統合し、フレームワークごとのセットアップはGo APIにSwaggerを追加するで扱われています。
もう一つの軸:コンテキスト伝播。GinとEchoはcontext.Contextを独自のコンテキスト型内でラップするため、ハンドラーにキャンセルを渡すには抽出ステップが必要です。これらのラッパーのいずれかを横断してキャンセル、タイムアウト、値をまっすぐ保つためのパターンは、Go context.Contextを正しく使うにあります。
誰も設定しないタイムアウト
この比較のすべてのスタックは、設定しない限りサーバータイムアウトをゼロのデフォルトに保ちます——ゼロとは制限なしを意味します。接続を開き何も送信しないクライアントは、無限にゴルーティンとファイルディスクリプタを保持します:
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
srv.ListenAndServe()
最初に設定すべきはReadHeaderTimeoutです——長期間のアップロードを壊すような包括的なReadTimeoutとは異なり、slowloris様の接続枯渇を制限します。WriteTimeoutは最遅の正当なレスポンスをカバーする必要があり、これはコピー&ペーストの定数ではなく、タイムアウト予算に紐づいたポリシーの決定です。同じ規律がフレームワークごとに適用されます:Gin、Echo、Fiberはすべてサーバーレベルのタイムアウト設定を受け入れますが、デフォルトで有効にするものではありません。
選択
またはゼロ依存要件か?} B -- はい --> C[net/http ServeMux] B -- いいえ --> D{ビルトインバインディング、
バリデーション、大きなミドルウェアカタログを求るか?} D -- いいえ --> E[chi + net/http] D -- はい --> F{チームの規約は?} F -- エラー返却ハンドラー --> G[Echo] F -- 最大のエコシステム、Ginのイディオム --> H[Gin] A --> I{小さなレスポンス、極限のスループット、
HTTP/2なし、アップストリームTLS終了か?} I -- すべて真 --> J[Fiber] I -- いいえ --> E
| 状況 | 選択 |
|---|---|
| 内部サービス、CLIエンドポイント、プラグイン、ライブラリ | net/httpのみ |
| ほとんどのAPI — フレームワークロックインなしの構造 | net/http上のchi |
| 公開API、大規模チーム、ボックスアウトのバリデーションとバインディング | Gin |
| 公開API、中央集権型エラー変換、一貫性のあるコンテキストAPI | Echo |
| 検証済みのルーティング/シリアライゼーションボトルネック、小さなJSON、HTTP/2なし | Fiber |
2026年の新規サービスにおいて、誠実なデフォルトを名指ししなければならない場合:net/httpから始め、ミドルウェアやルートグループが痛くなったらchiを追加し、バインディング、バリデーション、またはチームの規約がフレームワークアイデンティティを正当化する時までGinやEchoには移行しません。Fiberは専門的なツールであり、スループットが問題であると証明されたときにベンチマーク対照する価値があり、それが請求する互換性の代償に対して価格づけて判断されます。
スタック間の移行
移行コストはハンドラーシグネチャの分岐を追従します。標準ライブラリ ↔ chiは機械的です:ハンドラーは同じシグネチャを保持し、ルートはmux.HandleFuncパターンからr.Getに移動し、ミドルウェアはchiスタックに付けられます。Gin ↔ Echoはコンテキスト型の翻訳です——c.Param、c.JSON、ミドルウェアロジックはほぼ行単位で移植できます。FiberへのまたはFiberからの移動はHTTPレイヤーの書き直しです、なぜなら*http.Requestベースのコード、net/httpミドルウェア、h2の前提はfasthttp境界を生き延びないためです。最も痛みが少ないルートは、ビジネスロジックをフレームワークフリーのパッケージに保持し、トランスポートレイヤーを再生成することであり、これは次の移行を安価にする構造でもあります。