新しいTypeScript APIを始めるとき、Express 5とHonoのどちらを選ぶべきか。2026年時点では、これは「古いフレームワークか新しいフレームワークか」ではなく、設計思想の選択になっている。
Express 5は2025年3月にnpmのデフォルトlatestになり、プロジェクトとしても活発に動いている。一方Honoは、Cloudflare Workersに特化したルーターから、Cloudflare Workers・Cloudflare Pages・Node.js・Deno・Bun・AWS Lambda・Vercel・Fastly Computeに公式アダプターを提供するフレームワークへと成長した。
「Honoは速い」という記事は多い。しかし、その速度差が実際のアプリケーションでどれだけ意味を持つかは、ほとんど検証されていない。両方のフレームワークで同じAPIを実装し、Node.js 22でベンチマークを取った結果を共有する。
結論から先に
| 状況 | 選択 | |---|---| | 新規TypeScript API、ランタイム移植性が必要 | Hono | | Cloudflare Workers、Deno、Bun | Hono | | 依存パッケージを最小にしたい | Hono | | 既存のExpressコードベース | Expressのまま | | Node.js専用、middlewareが多い | Express | | Express 4からのアップグレード | Express 5(Honoへの移行より低リスク) |
2026年にこの比較が変わった理由
「Expressはオワコン、Honoを使え」という記事が増えた時期があった。しかしExpress 5のリリースで状況が変わっている。
Express 5の主な変更点:
- 非同期ミドルウェアのエラーが自動的にエラーハンドラーへ渡される(
try/catchでラップしなくてよい) path-to-regexpv8への更新- LTSプランの公開
Express 5は「Expressが復活した」というより、「長年放置されていた課題を解消したメンテナンスリリース」に近い。ただしそれによって、新規プロジェクトでExpressを選ぶ合理性が再び生まれた。
設計の違い:Node.jsネイティブ vs Web Standards
ExpressはNode.jsのhttp.IncomingMessageとhttp.ServerResponseを直接扱う。Node.js専用の設計だが、変換オーバーヘッドがない。
HonoはWeb Fetch API(RequestとResponse)を中心に設計されている。ブラウザ・Cloudflare Workers・Deno・Node.jsで共通の仕様だ。Node.js上でHonoを動かすには@hono/node-serverというアダプターが必要で、このアダプターがNode.jsのhttpモジュールとFetch APIの間を橋渡しする。
この設計の違いが、後述するパフォーマンス特性・メモリ使用量・TypeScript体験・ランタイム移植性のすべての根拠になる。
実装:同じAPIを両方で作った
比較のために、以下のAPIを両フレームワークで実装した。
GET /health — ヘルスチェック(ミドルウェアなし)
GET /users/:id — 認証ミドルウェア + 5msの疑似DBアクセス
POST /users — 認証 + Zodバリデーション + 5msの疑似DBアクセス
Express 5:
import express, { Request, Response, NextFunction } from 'express'
import cors from 'cors'
import { z } from 'zod'
const app = express()
app.use(cors())
app.use(express.json())
interface AuthRequest extends Request {
user?: { id: string }
}
function auth(req: AuthRequest, res: Response, next: NextFunction) {
if (!req.headers['authorization']) return res.status(401).json({ error: 'Unauthorized' })
req.user = { id: '1' }
next()
}
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
})
app.get('/health', (req, res) => res.json({ status: 'ok' }))
app.get('/users/:id', auth, async (req: AuthRequest, res) => {
await new Promise(r => setTimeout(r, 5))
res.json({ id: req.params.id, name: 'Test User' })
})
app.post('/users', auth, async (req: AuthRequest, res) => {
const result = CreateUserSchema.safeParse(req.body)
if (!result.success) return res.status(400).json({ error: 'Invalid' })
await new Promise(r => setTimeout(r, 5))
res.status(201).json({ id: '123', ...result.data })
})
Hono 4:
import { Hono, type MiddlewareHandler } from 'hono'
import { cors } from 'hono/cors'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
type Variables = { user: { id: string } }
const app = new Hono<{ Variables: Variables }>()
app.use('*', cors())
const auth: MiddlewareHandler<{ Variables: Variables }> = async (c, next) => {
if (!c.req.header('authorization')) return c.json({ error: 'Unauthorized' }, 401)
c.set('user', { id: '1' })
await next()
}
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
})
app.get('/health', (c) => c.json({ status: 'ok' }))
app.get('/users/:id', auth, async (c) => {
await new Promise(r => setTimeout(r, 5))
return c.json({ id: c.req.param('id'), name: 'Test User' })
})
app.post('/users', auth, zValidator('json', CreateUserSchema), async (c) => {
const data = c.req.valid('json')
await new Promise(r => setTimeout(r, 5))
return c.json({ id: '123', ...data }, 201)
})
コード量は同程度。違いはTypeScript体験で現れる。
TypeScript体験の差
Expressでは、ミドルウェアがreqオブジェクトに追加したプロパティ(例:req.user)に型をつけるために、インターフェースの拡張が必要になる。
// 型は手動で定義する
interface AuthRequest extends Request {
user?: { id: string }
}
// ルートハンドラーでキャスト
app.get('/users/:id', auth, async (req: AuthRequest, res) => {
// authミドルウェアが実行済みかどうか、TypeScriptは確認できない
const user = req.user
})
ミドルウェアチェーンに型情報は伝わらない。authが実際に実行されたかどうかはどちらのフレームワークもコンパイル時には検証できない。Honoの優位は検証ではなく、型を得るためのボイラープレートが不要という点にある。
Honoではアプリのジェネリクスでコンテキストの型を定義する。インターフェース拡張は不要だ。
type Variables = { user: { id: string } }
const app = new Hono<{ Variables: Variables }>()
// インターフェース拡張なしで c.get('user') が { id: string } を返す
app.get('/users/:id', auth, async (c) => {
const user = c.get('user') // { id: string }
})
バリデーションも同様で、zValidatorを通すとc.req.valid('json')がZodスキーマから推論された型を返す。Expressのreq.bodyは検証後もanyのまま。
TypeScriptを重視するチームにとって、Honoのジェネリクス設計はインターフェース拡張やキャストの記述量を減らす。
ミドルウェアエコシステム
Expressの最も強い優位点はここにある。
helmet・express-rate-limit・passport・multer・express-session・morganなど、長年本番環境で使われてきたミドルウェアが豊富にある。サードパーティのデータベースクライアントや認証プロバイダーも、Expressミドルウェアとして統合を提供していることが多い。
Honoにも標準ミドルウェアは揃っている(CORS・ロギング・JWT・レートリミット・キャッシュ制御など)。一般的なユースケースは標準ミドルウェアで対応できる。ただし、Passportのストラテジーや複雑なセッション管理など、Expressの固有ミドルウェアに相当するものはまだ少ない。
Honoの標準ミドルウェアで要件が満たせる場合、エコシステムの差は小さい。特定のExpressミドルウェアが必要な場合は、移行コストに含めて考える必要がある。
ベンチマーク結果
環境:
| 項目 | 値 | |---|---| | Node.js | v22.17.1 | | Express | 5.2.1 | | Hono | 4.13.1 | | @hono/node-server | 1.19.17 | | ベンチマークツール | autocannon、同時接続10 | | 計測時間 | 各シナリオ10秒 | | プラットフォーム | macOS、Apple Silicon(arm64) | | 日付 | 2026-08-08 |
ロギングミドルウェアは計測中無効にした。両サーバーは別プロセスで起動した。各シナリオの計測は1回(10秒連続計測)。Linux x86環境では絶対値が異なる可能性があるが、相対的な傾向は変わらないと見ている。
結果:
| シナリオ | フレームワーク | req/s | p50 (ms) | p99 (ms) | |---|---|---|---|---| | A: GET /health(純粋ルーティング) | Express 5 | 4,705 | 2 | 6 | | A: GET /health(純粋ルーティング) | Hono 4 | 10,579 | 0 | 2 | | B: GET /users/:id(認証 + 5ms非同期) | Express 5 | 1,502 | 6 | 10 | | B: GET /users/:id(認証 + 5ms非同期) | Hono 4 | 1,575 | 6 | 12 | | C: POST /users(認証 + Zod + 5ms非同期) | Express 5 | 1,511 | 6 | 17 | | C: POST /users(認証 + Zod + 5ms非同期) | Hono 4 | 1,550 | 6 | 14 |
数字が示すこと:
シナリオAは、フレームワーク固有のルーティングオーバーヘッドのみを計測している。Honoは2.25倍速い。
しかしシナリオBとCが本質を示す。5msの非同期処理(典型的なDBクエリ1回に相当)を追加した途端、差は4.9%と2.6%になる。1,500 req/s換算で約70〜75 req/sの差だ。
ほとんどのAPIエンドポイントは実際の処理を行う。DBクエリ・外部API呼び出し・シリアライズ・ビジネスロジックがレイテンシの大部分を占める。フレームワークのルーティングオーバーヘッドは、5ms程度の非同期処理が入ると誤差の範囲に収まる。
メモリ使用量(RSS、ベンチマーク後):
| フレームワーク | RSS | |---|---| | Express 5 | 51 MB | | Hono 4(Node.js) | 133 MB |
これは直感に反する結果だ。HonoはCloudflare Workersでは非常に軽量だが、Node.js上では@hono/node-serverアダプターがFetch APIとNode.jsのhttpモジュールを橋渡しするため、その分のオーバーヘッドが加わる。ExpressはNode.jsネイティブAPIを直接使用するため、RSS消費が少ない。
注意:RSSはベンチマーク完了後に1回計測したもの。GCの状態によって変動する。Cloudflare Workers上では、Honoはアダプターなしで動作し、フットプリントは大幅に小さくなる。
依存パッケージ数
| フレームワーク(cors + zod含む) | 合計パッケージ数 | node_modulesサイズ | |---|---|---| | Express 5 | 68 | 8.8 MB | | Hono 4 | 4 | 8.1 MB |
Expressは自身の直接依存として28パッケージを持つ(body-parser・accepts・type-isなど)。これらの推移的依存を合わせると68パッケージになる。
Honoのコアパッケージは外部依存がゼロ。@hono/node-server・@hono/zod-validator・zodを加えても4パッケージ。
依存の少なさは、サーバーレスやエッジ環境でのコールドスタートに直接影響する。Node.jsの常時稼働サーバーでは起動時間の差は小さいが、AWS LambdaやCloudflare Workersでは意味を持つ。
ランタイム移植性
依存パッケージの差はHonoの最大の設計上の優位点につながる。同じアプリケーションコードが、複数のランタイムで動作する。
ExpressはNode.js APIに依存して書かれる。Cloudflare WorkersやDeno、Bunへ移行する場合、サーバーエントリーポイントだけでなくフレームワーク層全体の書き直しが必要になる。
Honoはルートハンドラー内でWeb Standard APIを使う。c.reqはCloudflare WorkersのRequestオブジェクトと同じ仕様だ。デプロイ先を変えるときは、エントリーポイントのアダプターを差し替えるだけでよい。
Node.js:
import { serve } from '@hono/node-server'
import { app } from './app'
serve({ fetch: app.fetch, port: 3000 })
Cloudflare Workers:
import { app } from './app'
export default { fetch: app.fetch }
app.tsのルートハンドラーは変更しない。Node.jsからエッジへの移行を将来の選択肢として残したいチームには、これは理論上の優位ではなく実際の利点になる。
詳細は「Cloudflare PagesからWorkersへの移行」を参照。HonoをWorkers上でDeployする手順も含んでいる。
既存のExpressアプリは移行すべきか
パフォーマンスだけを理由にした移行は勧めない。
実際の処理が入るシナリオでは、両フレームワークの差は5%以内だった。Honoへ移行しても遅いエンドポイントは速くならない。ボトルネックがDBクエリやキャッシュミスにある場合、フレームワーク層は制約ではない。
移行を検討する正当な理由:
- Cloudflare WorkersやDeno等のランタイム移植性が必要
- ミドルウェアチェーンを通じたTypeScript型安全性が重要
- 依存パッケージを最小化したい(68→4)
Expressに留まる正当な理由:
- 利用中のExpressミドルウェアに相当するHono実装がない
- Express 4→5のアップグレードで現在の課題が解決する(非同期エラーハンドリングなど)
- フレームワーク移行のコストがHonoの利点を上回る
Express 5には公式マイグレーションガイドがある。Express内でのアップグレードを先に検討する価値がある。
Fastifyについて
Node.js専用APIでは、Fastifyも有力な選択肢だ。JSONスキーマを活用したバリデーションとシリアライズが特徴で、プラグインエコシステムも成熟している。Node.js上の純粋なルーティング速度はExpressより大幅に速いとするベンチマーク結果が多い。なお本記事ではFastifyの計測は行っていない。
Fastifyが向くケース:Node.js専用で最大スループットが必要、JSONスキーマが許容できる。
HonoとFastifyの主な違い:FastifyはExpressと同様にNode.jsネイティブ設計。Cloudflare WorkersやDenoへの移行は、Honoと比べて大きな書き直しを伴う。ランタイム移植性の観点では、FastifyはExpressと同じカテゴリーに入る。
選択基準のまとめ
Honoを選ぶ場合:
- Cloudflare Workers・Deno・Bun、またはその将来の可能性が必要
- TypeScriptのミドルウェアチェーン型推論が重要
- 依存パッケージを最小化したい
- 新規APIで既存の資産がない
Expressを選ぶ場合:
- 特定のExpressミドルウェアが必要でHono版がない
- 既存のExpressコードベースをアップグレードする
- チームのExpressノウハウが深く、移行コストが利点を上回る
- Node.jsのみで運用、エッジランタイムの予定がない
移行しない場合:
- 移行理由がパフォーマンスだけ — 実際の差は5%未満
- ランタイム移植性もTypeScript DXの改善も必要がない
- 現在使用中のExpressミドルウェアのHono版がなく、代替実装コストが大きい
Honoを使ってSupabase AuthのJWT検証を実装する手順は「Hono Supabase Auth: JWTを検証する4つの方法」に詳しい。
ベンチマーク環境:Node.js v22.17.1、Express 5.2.1、Hono 4.13.1、macOS arm64、2026-08-08。