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構築にあります。ここでは焦点はスタック自体に留めます。

5つの3Dガラスブロックが発光する線で接続されている、GoのHTTPスタックそれぞれに対応している

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はすべてサーバーレベルのタイムアウト設定を受け入れますが、デフォルトで有効にするものではありません。

選択

flowchart TD A[新しいGo HTTPサービス] --> B{内部ツール、プラグイン、
またはゼロ依存要件か?} 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境界を生き延びないためです。最も痛みが少ないルートは、ビジネスロジックをフレームワークフリーのパッケージに保持し、トランスポートレイヤーを再生成することであり、これは次の移行を安価にする構造でもあります。

有用的リンク

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。