Back

Express アプリを Hono に移行する際のヒント

Express アプリを Hono に移行する際のヒント

Express API を保守していて、Hono への移行を検討しているなら、まず理解しておくべきは、これは単純な置換による移行ではないということです。Express は Node の http モジュールと独自の req/res オブジェクトの上に構築されています。一方、Hono は Fetch API と Web 標準の上に構築されています。この違いがすべて—ルーティング、ミドルウェア、リクエスト処理、レスポンス—に影響します。

着手する前に押さえておくべきポイントを以下にまとめます。

重要なポイント

  • Express と Hono は根本的に異なる基盤、すなわち Node の http モジュールと Fetch API および Web 標準の上に成り立っています。
  • Hono は Express の別個の reqres オブジェクトを単一のコンテキストオブジェクト c に置き換え、ハンドラは Response を返す必要があります。
  • ミドルウェアのシグネチャも異なります。Express は (req, res, next) を使うのに対し、Hono は (c, next) を使い、next() を await します。
  • ボディのパースはグローバルミドルウェアではなく、Hono のハンドラ内で直接行います。
  • ステートレスな JSON ルートから始める段階的な移行は、全面的な書き直しよりもはるかに安全です。

まずアーキテクチャの違いを理解する

Express は Node の IncomingMessageServerResponse をラップしています。すべての req および res オブジェクトは Node 固有のものです。対照的に、Hono は標準的な Request および Response オブジェクト—ブラウザや Cloudflare Worker で使うものと同じ—を扱います。

Node.js 上で Hono を動かす場合は、@hono/node-server アダプターを使って橋渡しします。ただし、ハンドラ自体はランタイム非依存のままです。

// Express
app.get('/users/:id', async (req, res) => {
  const user = await db.findById(req.params.id)
  res.json(user)
})

// Hono
app.get('/users/:id', async (c) => {
  const user = await db.findById(c.req.param('id'))
  return c.json(user)
})

形は似ていますが、c(コンテキストオブジェクト)が reqres の両方を置き換えています。res のメソッドを呼び出すのではなく、Response を返す形になります。

Node.js API を Hono に移行する: ステートレスなルートから始める

Express アプリを Hono に移植する際、最も安全な出発点はステートレスで JSON のみを扱うルートです—ファイルシステムアクセスや Node 固有のストリーム、セッションミドルウェアを伴わないものです。これらはきれいに変換できます。

Node 固有の API(req.socketres.localsres.sendFile)に依存するルートはより慎重な検討が必要です。本格的に移行に踏み切る前に、まずそれらを切り分け、扱う対象を明確にしておきましょう。

Hono ミドルウェアの移行: 互換性を当然視しない

Express のミドルウェアは (req, res, next) パターンに従います。Hono のミドルウェアは (c, next) を使い、通常は await next() を呼び出します。両者は互換性がありません

// Express ミドルウェア
app.use((req, res, next) => {
  req.startTime = Date.now()
  next()
})

// Hono での同等のもの
app.use(async (c, next) => {
  c.set('startTime', Date.now())
  await next()
})

一般的なパッケージについては、Hono に公式の同等品があります:

ExpressHono の同等品
cors()hono/cors
helmet()hono/secure-headers
express.json()c.req.json() 経由でビルトイン
morganhono/logger またはカスタムミドルウェア

Express のミドルウェアをラップしようとするのではなく、意図的に書き直しましょう—抽象化がきれいに保たれることは稀です。

リクエストボディとレスポンスの扱い

Express 5 では、ボディのパースは依然としてミドルウェアベースです。Hono では、ボディをハンドラ内で直接パースします:

// Hono のボディパース
app.post('/items', async (c) => {
  const body = await c.req.json()
  return c.json({ received: body }, 201)
})

レスポンスは常に return するもので、変更(mutate)するものではありません。res.status(201).json(...) のような書き方はなく、代わりにステータスを c.json() の第 2 引数として渡します。

エラーハンドリング

Express は 4 引数のエラーハンドラ (err, req, res, next) を使います。Hono は app.onError を使います:

app.onError((err, c) => {
  console.error(err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

一度にではなく、段階的に移行する

全面的な書き直しがスムーズに進むことは稀です。より良いアプローチは:

  1. 一度に 1 つの独立したルートグループを移植する。
  2. 移行期間中は Hono を Express と並行して動かす。
  3. ミドルウェアは明示的に書き直す—ラップしない。
  4. Node 固有の依存関係(ファイル処理、レガシー認証)は最後に回す。

まとめ

Express アプリの Hono への移植は、2 つのフレームワークが異なる基盤の上に成り立っていることを受け入れれば、難しくはありません。ルーティング構文は十分に馴染みがあり、ほとんどのルートは数分で変換できます。本当に労力を要するのは、ミドルウェアと、Node 固有の API を直接触っている部分です。それらに対して計画的に取り組めば、移行は十分に管理可能なものになります。

FAQ

はい。よくあるパターンは、両者の前段にリバースプロキシや小さな Node のエントリポイントを置き、特定のパスを Hono アプリにルーティングし、残りは Express に任せる方法です。これにより、ルートグループを 1 つずつ移行でき、問題が起きた場合にも素早くロールバックできます。リスクの高い一括切り替えに踏み切る必要はありません。

Hono は Node.js、Bun、Deno、Cloudflare Workers、AWS Lambda、その他いくつかのランタイムで動作します。Node 上では、@hono/node-server アダプターを使って、Fetch API モデルと Node の http モジュールを橋渡しします。同じハンドラのコードがランタイム間で移植可能であり、これが Express に対する Hono の大きな利点の 1 つです。

基本的にはありません。Express のミドルウェアは Node の req と res オブジェクト、そして next コールバックに依存していますが、Hono のミドルウェアは Fetch スタイルのコンテキスト上で動作します。コミュニティ製のアダプターもいくつか存在しますが、壊れやすい傾向があります。推奨されるのは Hono の API に合わせてミドルウェアを書き直すことです。よくあるニーズの多くは、すでに公式またはビルトインの同等品でカバーされています。

Hono は一般に Express より高速で、特にルーティングと JSON レスポンスでその傾向が顕著です。これは軽量なルーターと Fetch ベースの設計のおかげです。実際にどの程度の向上が得られるかはワークロード次第で、通常はデータベースクエリや外部呼び出しが支配的になります。パフォーマンスは移行による有用な副次効果と捉えるべきで、ベンチマークでルーティングのオーバーヘッドが本当のボトルネックだと示されない限り、主な動機にすべきではありません。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay