express vs hono 徹底比較

express の詳細hono の詳細
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

Express vs Hono — どちらのWebフレームワークを選ぶべきか?

1. 結論

新規プロジェクトで軽量・高速なAPIを構築するなら Hono、既存のNode.jsエコシステムとの互換性や豊富なミドルウェア資産を活かしたいなら Express を選ぶのが現時点での最適解です。Hono は Web Standards ベースでマルチランタイム対応という次世代の設計思想を持ち、Express は10年以上の実績と圧倒的なエコシステムを誇ります。プロジェクトの要件・デプロイ先・チームの習熟度に応じて選択してください。


2. 比較表

観点ExpressHono
初回リリース2010年2022年
最新バージョン(2025年時点)v5.x(v4.x が主流)v4.x
バンドルサイズ(minified)約 200 KB+約 14 KB(hono/tiny で約 3 KB)
TypeScript 対応@types/express が必要ネイティブ対応(型推論が強力)
ランタイム対応Node.js(主)Node.js / Deno / Bun / Cloudflare Workers / AWS Lambda / Vercel 等
ベース仕様Node.js http モジュール独自APIWeb Standards(Request / Response / fetch
ルーティング性能十分高速(RegExp ベース)非常に高速(Trie ベースの RegExpRouter)
ミドルウェア数npm 上に数千規模公式・コミュニティ合わせて急成長中(数十〜百規模)
学習コスト低(情報量が圧倒的)低〜中(Express 経験者なら移行しやすい)
バリデーション統合外部ライブラリ(joi, zod 等)を自前で組み込みZod / Valibot との公式統合ヘルパーあり
RPC 機能なしhono/client による型安全な RPC
JSX サポートなし組み込み(hono/jsx
GitHub Stars(2025年時点)約 66,000+約 22,000+(急成長中)
週間ダウンロード数約 3,500万+約 100万+(急成長中)
ライセンスMITMIT

3. それぞれの強み

Express の強み

  • 圧倒的なエコシステム: passport(認証)、multer(ファイルアップロード)、cors、helmet など、あらゆるユースケースに対応するミドルウェアが揃っています。
  • 情報量の多さ: Stack Overflow の回答数、ブログ記事、書籍、Udemy 等の教材が桁違いに多く、トラブルシューティングで困ることがほぼありません。
  • 安定性と実績: Fortune 500 企業を含む無数のプロダクションで稼働しており、長期運用のノウハウが蓄積されています。
  • 採用市場での優位性: 「Express 経験あり」は Node.js エンジニアの事実上の共通言語であり、チームビルディングがしやすいです。
  • 柔軟なアーキテクチャ: "unopinionated" の名の通り、ディレクトリ構成やパターンを自由に設計できます。

Hono の強み

  • マルチランタイム対応: 一つのコードベースで Cloudflare Workers、Deno Deploy、Bun、AWS Lambda、Vercel Edge Functions など多様な環境にデプロイできます。
  • Web Standards 準拠: Request / Response / fetch API といった標準仕様に基づいているため、ランタイムのロックインを避けられます。
  • 圧倒的な軽量さ: 最小構成で約 3 KB。エッジコンピューティングやサーバーレス環境でのコールドスタートに大きく貢献します。
  • TypeScript ファーストの型推論: ルーティングパラメータ、バリデーション結果、ミドルウェアのコンテキストまで型が自動推論されます。
  • 型安全な RPC クライアント: hono/client を使えば、サーバー側のルート定義からクライアント側の型を自動生成でき、tRPC のような開発体験が得られます。
  • 組み込み JSX: React や Preact なしで JSX を使ったサーバーサイドレンダリングが可能です。
  • 高速なルーティング: ベンチマークで Express を大幅に上回るパフォーマンスを示しています。

4. コード例で比較

4-1. 基本的な REST API

Express

// express-app.ts
import express, { Request, Response } from "express";

const app = express();
app.use(express.json());

interface User {
  id: string;
  name: string;
  email: string;
}

const users: User[] = [
  { id: "1", name: "田中太郎", email: "tanaka@example.com" },
  { id: "2", name: "佐藤花子", email: "sato@example.com" },
];

// 一覧取得
app.get("/api/users", (_req: Request, res: Response) => {
  res.json(users);
});

// 個別取得
app.get("/api/users/:id", (req: Request, res: Response) => {
  const user = users.find((u) => u.id === req.params.id);
  if (!user) {
    res.status(404).json({ message: "User not found" });
    return;
  }
  res.json(user);
});

// 新規作成
app.post("/api/users", (req: Request, res: Response) => {
  const { name, email } = req.body;
  // バリデーションは自前で実装する必要がある
  if (!name || !email) {
    res.status(400).json({ message: "name and email are required" });
    return;
  }
  const newUser: User = {
    id: String(users.length + 1),
    name,
    email,
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

app.listen(3000, () => {
  console.log("Express server running on http://localhost:3000");
});

Hono

// hono-app.ts
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const app = new Hono();

interface User {
  id: string;
  name: string;
  email: string;
}

const users: User[] = [
  { id: "1", name: "田中太郎", email: "tanaka@example.com" },
  { id: "2", name: "佐藤花子", email: "sato@example.com" },
];

// 一覧取得
app.get("/api/users", (c) => {
  return c.json(users);
});

// 個別取得
app.get("/api/users/:id", (c) => {
  const user = users.find((u) => u.id === c.req.param("id"));
  if (!user) {
    return c.json({ message: "User not found" }, 404);
  }
  return c.json(user);
});

// 新規作成(Zod バリデーション統合)
const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

app.post(
  "/api/users",
  zValidator("json", createUserSchema),
  (c) => {
    const { name, email } = c.req.valid("json"); // ← 型が自動推論される
    const newUser: User = {
      id: String(users.length + 1),
      name,
      email,
    };
    users.push(newUser);
    return c.json(newUser, 201);
  }
);

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.log("Hono server running on http://localhost:3000");
});

ポイント: Hono では zValidator を使うことで、バリデーションと型推論がルート定義に自然に統合されます。Express では同等のことを実現するために、ミドルウェアを自作するか外部ライブラリを別途組み込む必要があります。


4-2. ミドルウェア(認証)の実装

Express

import express, { Request, Response, NextFunction } from "express";

// カスタム型の拡張が必要
declare global {
  namespace Express {
    interface Request {
      userId?: string;
    }
  }
}

function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token || token !== "valid-token") {
    res.status(401).json({ message: "Unauthorized" });
    return;
  }
  req.userId = "user-123"; // 型拡張が必要
  next();
}

const app = express();

app.get("/api/profile", authMiddleware, (req: Request, res: Response) => {
  // req.userId の型は string | undefined(型安全ではない)
  res.json({ userId: req.userId });
});

Hono

import { Hono } from "hono";
import { createMiddleware } from "hono/factory";

// ミドルウェアで設定する変数の型を定義
type AuthEnv = {
  Variables: {
    userId: string;
  };
};

const authMiddleware = createMiddleware<AuthEnv>(async (c, next) => {
  const token = c.req.header("Authorization")?.replace("Bearer ", "");
  if (!token || token !== "valid-token") {
    return c.json({ message: "Unauthorized" }, 401);
  }
  c.set("userId", "user-123"); // 型安全に変数をセット
  await next();
});

const app = new Hono();

app.get("/api/profile", authMiddleware, (c) => {
  const userId = c.get("userId"); // ← string 型として推論される
  return c.json({ userId });
});

ポイント: Express では Request オブジェクトの型拡張(declare global)が必要で、型安全性に限界があります。Hono では Variables 型をジェネリクスで渡すことで、ミドルウェアが設定した値をハンドラ側で型安全に取得できます。


4-3. Hono 独自機能 — 型安全 RPC クライアント

// server.ts(Hono サーバー側)
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const app = new Hono()
  .get("/api/users", (c) => {
    return c.json([
      { id: "1", name: "田中太郎" },
      { id: "2", name: "佐藤花子" },
    ]);
  })
  .post(
    "/api/users",
    zValidator("json", z.object({ name: z.string(), email: z.string().email() })),
    (c) => {
      const body = c.req.valid("json");
      return c.json({ id: "3", ...body }, 201);
    }
  );

// 型をエクスポート
export type AppType = typeof app;
// client.ts(クライアント側 — フロントエンドや別サービス)
import { hc } from "hono/client";
import type { AppType } from "./server";

const client = hc<AppType>("http://localhost:3000");

async function main() {
  // GET /api/users — レスポンスの型が自動推論される
  const res = await client.api.users.$get();
  const users = await res.json();
  // users の型: { id: string; name: string }[]

  // POST /api/users — リクエストボディの型もチェックされる
  const createRes = await client.api.users.$post({
    json: { name: "鈴木一郎", email: "suzuki@example.com" },
  });
  const newUser = await createRes.json();
  // newUser の型: { id: string; name: string; email: string }
}

ポイント: Express には同等の機能がなく、tRPC や OpenAPI + コード生成ツールなど別のソリューションを組み合わせる必要があります。


5. どちらを選ぶべきか — ユースケース別の推奨

Express を選ぶべきケース

ユースケース理由
既存の Express アプリの保守・拡張移行コストに見合わないケースが多い
passport 等の特定ミドルウェアに強く依存Hono に同等のエコシステムがまだない
チームに Express 経験者が多い学習コストゼロで即戦力になる
Node.js 固有の API(Stream, Buffer 等)を多用Express の方が自然に扱える
大量の日本語・英語ドキュメントが必要情報量で Express が圧倒的に優位

Hono を選ぶべきケース

ユースケース理由
**Cloudflare Workers / Deno