every Tech Blog

株式会社エブリーのTech Blogです。

Echo v5 がリリースされました

目次

はじめに

こんにちは、開発本部開発1部トモニテグループのエンジニアの パンダム/rymiyamoto です。

2026/01/18 よりEcho v5がリリースされました 🎉

弊社プロダクトの多くが依存しているフレームワークなだけに、最新バージョンへの移行パスをいち早く探っておきたいところ。 さっそく、主要な変更点や所感をレポートします。

Echoを使って開発している方も多いと思いますので、その手助けになればと思います。

github.com

注意事項

本記事はEcho v4の基本的な知識がある読者を対象としています。
また執筆は2026/01/22時点のもので、Go 1.25.6をベースにしています。

リリース直後のv5には、今後も破壊的な変更が加わる余地が残されています。そのため公式からは、商用利用については 2026/3/31まで待機すること が推奨されている点に注意が必要です。
(ちなみにv4のサポートは2026/12/31までです)

公式からの引用↓

  • v5.0.0 was release on 2026-01-18.
  • v4 will be supported with security updates and bug fixes until 2026-12-31.
  • Until 2026-03-31, any critical issues requiring breaking API changes will be addressed, even if this violates semantic versioning.
  • If you are using Echo in a production environment, it is recommended to wait until after 2026-03-31 before upgrading.

Echo v5の主な変更点

今回の変更点をまとめてみると、主に以下の5点に集約されます。

  • 【破壊的変更】echo.Context がインターフェースから構造体に変更
    • 将来的な機能追加を容易にするための大きな設計変更です
  • 【新機能】Router のインターフェース化による拡張性の向上
    • 独自のルーター実装が可能になり、正規表現ルーティングなども導入しやすくなります
  • 【仕様変更】サーバー起動設定の構造体化 (StartConfig)
    • アドレスやタイムアウト設定が構造体に集約され、設定の可読性が向上します
  • 【破壊的変更】Go標準ライブラリ log/slog をネイティブサポート
    • サードパーティ製ライブラリなしで、モダンな構造化ログが扱えるようになります
  • 【破壊的変更】APIの一貫性向上とメソッド整理
    • 全体的なシグネチャが見直され、より洗練された開発体験を提供します

概要からみてこれまでv4の期間が長かったのもあり大きな進歩だなと感じました。
今までは標準で対応しづらい部分はサードパーティを利用していたので、ログ周りが自分としては一番ありがたいです。 他にも設定周りが統一化されているのでよりコードの可読性が向上しそうな予感がしますね。

もちろん他にもいくつかAPIの変更点がありますが、詳細については実際にv4で動いているコードの修正をしながら気づいた部分を紹介していきます!
(紹介しきれていない部分は下記のドキュメントを読んでみてください)

github.com

Echo v4からv5への移行しながら変更点を確認する

バージョン更新とecho.Contextの変更

公式に記載されている通りの方法でバージョン更新や最低限の修正を行うことができます。

# v4 から v5 への移行
go get github.com/labstack/echo/v5

# サードパーティの更新(対応されているサードパーティ系のライブラリは更新)
go get github.com/labstack/echo-contrib
go get github.com/labstack/echo-jwt/v5

# 一括置換
# echo.Context ->  *echo.Context
find . -type f -name "*.go" -exec sed -i 's/ echo.Context/ *echo.Context/g' {} +
# echo/v4 -> echo/v5
find . -type f -name "*.go" -exec sed -i 's/echo\/v4/echo\/v5/g' {} +

github.com

実際のコードの変更は以下のようになり、大多数の変更箇所になるかと思います。

// v4
func MyHandler(c echo.Context) error {
    return c.JSON(200, map[string]string{"hello": "world"})
}

// v5
func MyHandler(c *echo.Context) error {
    return c.JSON(200, map[string]string{"hello": "world"})
}

これだけだと型定義が変わっただけか〜となりますが、この変更による恩恵は新機能追加時に破壊的な変更にならないようにするための大事な変更と言えます。

まずecho.Contextがインターフェースだった場合、将来的に新しい機能(メソッド)を追加すると、そのインターフェースを実装しているすべてのコード(自作のContextラッパーやモックなど)がコンパイルエラーになります。 これは「破壊的変更」にあたるため、次のメジャーバージョンアップ(v6~)まで機能追加がしにくくなります。
構造体(*echo.Context)に変更することで、本体に新しいメソッドを追加しても、既存の利用側のコードはそのまま動作します。 これにより、v5の期間中にマイナーアップデートで便利な新機能をどんどん追加できるようになります。

Routerのカスタマイズ (Interface + DefaultRouter)

Routerのインターフェース化により、独自のルーター実装が可能になりました。

これにより、正規表現ルーティングや、特定のアプリケーションでは不要な機能(ワイルドカードや 405 Method Not Allowed のハンドリングなど)を削ぎ落とし、より効率的なルーティング処理を実現できるようになるのではないかと思います。 まだこれからだとは思いますが、 RouterConfig が拡張されていくことにより様々な恩恵を受けやすくなりそうです。

// v4
type Router struct { ... }

func NewRouter(e *Echo) *Router
func (r *Router) Add(method, path string, h HandlerFunc)
func (r *Router) Find(method, path string, c Context)
func (r *Router) Reverse(name string, params ...interface{}) string
func (r *Router) Routes() []*Route

// v5
type Router interface {
    Add(routable Route) (RouteInfo, error)
    Remove(method string, path string) error
    Routes() Routes
    Route(c *Context) HandlerFunc
}

type DefaultRouter struct { ... }

func NewRouter(config RouterConfig) *DefaultRouter
func NewConcurrentRouter(r Router) Router  // NEW

type RouterConfig struct {
    NotFoundHandler           HandlerFunc
    MethodNotAllowedHandler   HandlerFunc
    OptionsMethodHandler      HandlerFunc
    AllowOverwritingRoute     bool
    UnescapePathParamValues   bool
    UseEscapedPathForMatching bool
}

github.com

StartConfigを用いたサーバー起動

新しく追加された echo.StartConfig 構造体を使用する形式でもサーバーを起動することができます。
もちろんこれまで通りの echo.Start を使用することもできますが、アドレス指定が引数ではなく設定から指定できるのは使いやすそうです。

以下は StartConfig を使用した場合の例です。

// v5: echo.StartConfig を使用する
func main() {
        e := echo.New()
        e.Use(middleware.RequestLogger())

        e.GET("/", func(c *echo.Context) error {
                return c.String(http.StatusOK, "Hello, World!")
        })

        ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
        defer stop()

        sc := echo.StartConfig{
                Address:         ":1323",
                GracefulTimeout: 10 * time.Second,
        }

        if err := sc.Start(ctx, e); err != nil {
                log.Fatal(err)
        }
}
$ go run server.go
{"time":"2026-01-20T23:28:54.719092+09:00","level":"INFO","msg":"Echo (v5.0.0). High performance, minimalist Go web framework https://echo.labstack.com","version":"5.0.0"}
{"time":"2026-01-20T23:28:54.719326+09:00","level":"INFO","msg":"http(s) server started","address":"[::]:1323"}
{"time":"2026-01-20T23:29:11.2848+09:00","level":"INFO","msg":"REQUEST","method":"GET","uri":"/","status":200,"latency":3000,"host":"localhost:1323","bytes_in":"","bytes_out":13,"user_agent":"curl/8.7.1","remote_ip":"::1","request_id":""}

github.com

デフォルトロガーがslog.Loggerに変更

これまではGoの標準のログを少し拡張したシンプルなloggingパッケージを使っていましたが、slogを使うようになり、ログの設定でそのままslogの設定が利用できるようになりました。

これによりこれまでslog-echoのようなサードパーティを使ってslog利用していたように拡張する必要がなくなり、ロギング設定がより直感的になって個人的には一番嬉しいポイントになりました。

// v4
type Echo struct {
    Logger Logger  // Custom interface with Print, Debug, Info, etc.
}

// v5
type Echo struct {
    Logger slog.Logger  // slog.Logger interface
}

実際にslogを使ってログ設定をカスタマイズする場合は以下のようになります。

skipper := func(c *echo.Context) bool {
        return c.Request().URL.Path == "/health"
}
e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
    Skipper:     skipper, // ヘルスチェックのリクエストはログに含めない
    LogStatus:   true,
    LogURI:      true,
    LogError:    true,
    HandleError: true, // エラーをグローバルエラーハンドラに転送し、適切なステータスコードを決定できるようにします。
    LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error {
        if v.Error == nil {
            logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST",
                slog.String("uri", v.URI),
                slog.Int("status", v.Status),
            )
        } else {
            logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR",
                slog.String("uri", v.URI),
                slog.Int("status", v.Status),
                slog.String("err", v.Error.Error()),
            )
        }
        return nil
    },
}))

// start server...

echo.labstack.com

レスポンス情報の取得方法 (UnwrapResponse)

ミドルウェア等でレスポンスの書き込みサイズやステータスコードを参照する場合、これまでは c.Response() のフィールドに直接アクセスしていましたが、v5では echo.UnwrapResponse ヘルパーを使用する必要があるので修正が必要になります。

// v5
resp, err := echo.UnwrapResponse(c.Response())
if err == nil {
        // resp.Size      <-- 書き込まれたバイト数
        // resp.Status    <-- ステータスコード
        // resp.Committed <-- クライアントに送信済みかどうか
        fmt.Printf("Status: %d, Size: %d\n", resp.Status, resp.Size)
}

github.com

この変更に伴う修正には正直、かなり手こずりました。いざv5の変更を当ててビルドしてみると、あちこちでヘルパーがエラーを吐き出しレスポンスの中身を見ている処理の見直しがいるのかなと疑問に思い、一体どうすれば…と右往左往する羽目になりました 😇
しかし、APIの変更点を見直したり実際のv5のコードを追ってみるなどの試行錯誤の末にこの UnwrapResponse の存在に気づきました

この変更には最初こそ戸惑いましたが、val, err := func() というGo言語の標準的なエラーハンドリングパターンに統一されたことで、変更の意図が腹落ちしました。 v4までではフィールド直接参照は手軽な反面、内部実装への依存度が高く、将来的な変更に弱い側面があります。
今回ヘルパー関数を経由する形になったことで、内部構造が隠蔽され、仮に取得に失敗してもエラーとして安全に検知できる(堅牢性が高まる)ようになっています。 また、このアプローチはGo1.20で標準ライブラリに追加された http.ResponseController の設計思想とも通じる部分があり、EchoがGoのモダンな作法に追従しようとしている姿勢がうかがえます。手間は増えましたが、長期的な保守性を高めるための洗練された変更と言えそうです。

URLパラメータの埋め込み方法変更

テストのとき、URL内にリソースのIDを埋め込んで実行する際によく使用していたコードが変更されています。
v5のほうが1行でまとまり直感的に書けるようになっており、これまでのコードよりも可読性が向上しています。

// v4
c.SetParamNames("id")
c.SetParamValues("1")

// v5
c.SetPathValues(echo.PathValues{{Name: "id", Value: "1"}})

github.com

こちらもぱっと調べて出てこなかったので実際に修正してみて気づいた部分になりました。

echo.POSTのような http.MethodXXX のヘルパーが廃止

これまでHTTPメソッドのヘルパーがあったのですが、v5では廃止されています。
そのため、echo.POST などのヘルパーを http.MethodPost のように書き換える必要があります。
(何故かここはドキュメントにもAPI変更にも記載がないところでした)

追記(2026/01/29) 公式のドキュメントの方にも記載されました

github.com

以下がv4の時のコードです。

// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
const (
        CONNECT = http.MethodConnect
        DELETE  = http.MethodDelete
        GET     = http.MethodGet
        HEAD    = http.MethodHead
        OPTIONS = http.MethodOptions
        PATCH   = http.MethodPatch
        POST    = http.MethodPost
        // PROPFIND = "PROPFIND"
        PUT   = http.MethodPut
        TRACE = http.MethodTrace
)

github.com

追記(2026/01/29)

公式のドキュメントの方にも記載されました

github.com

まとめ

今回はEcho v5がリリースされたので触れてみました。

v5の新しい機能に触れてみて、その利便性やコードの読みやすさが格段に向上しているのを肌で感じました。これは開発体験を大きく変える可能性を秘めていると思います。

実際に社内のプロダクトで実験しながらアップデート内容に触れてみましたが、試した結果、まだまだ修正が必要な箇所が多く、エラーの解消や動作検証に膨大な時間を費やしました。 正直なところ、完全に商用利用できる状態にするには、まだ多くの修正と検証が必要だと痛感しています。 この道のりは決して楽ではありませんが、v5がもたらす開発体験の向上を考えると、乗り越える価値は十分にあると感じています。

ガッツリとした変更ありつつもv4での記法がすべて変わるわけではないので、地道に修正していく必要があります。

まだまだリリースして日が浅く、公式含めサードパーティ系も更新が活発になってくるかと思うので最新の情報においていかれないように食らいついていこうと思います。

最後まで読んでいただきありがとうございました!