h3 の使い方 — 高性能・ポータブルなHTTPフレームワーク
一言でいうと
h3 は、UnJS エコシステムが提供する軽量・高性能な HTTP フレームワークです。Node.js だけでなく、Cloudflare Workers・Deno・Bun・Vercel Edge Functions など、あらゆるランタイムで動作するポータブルな設計が最大の特徴です。
どんな時に使う?
- Nitro / Nuxt のカスタムサーバーAPI を書くとき — Nuxt 3 の
server/api/ディレクトリの裏側で動いているのが h3 です。仕組みを理解しておくとデバッグや拡張が格段に楽になります。 - エッジランタイム対応の軽量 API サーバーを構築したいとき — Express のような Node.js 固有の API に依存せず、Web 標準(
Request/Response)ベースで動作するサーバーが必要な場面に最適です。 - マイクロサービスや BFF(Backend for Frontend)を最小構成で立てたいとき — 依存が極めて少なく、起動が高速なため、コンテナやサーバーレス環境との相性が抜群です。
インストール
# npm
npm install h3
# yarn
yarn add h3
# pnpm
pnpm add h3
注意: この記事は h3 v2(2.0.1-rc.20)を基に執筆しています。v1 系とは API が大きく異なる部分があります。v1 からの移行時は公式マイグレーションガイドを必ず確認してください。
基本的な使い方
最もシンプルな HTTP サーバーの例です。
import { createApp, createRouter, defineEventHandler } from "h3";
const app = createApp();
const router = createRouter();
router.get(
"/",
defineEventHandler(() => {
return { message: "Hello, h3!" };
})
);
router.get(
"/users/:id",
defineEventHandler((event) => {
const id = getRouterParam(event, "id");
return { userId: id };
})
);
app.use(router);
export default app;
Node.js で起動する場合
import { createApp, createRouter, defineEventHandler, toNodeHandler } from "h3";
import { createServer } from "node:http";
const app = createApp();
const router = createRouter();
router.get(
"/",
defineEventHandler(() => {
return { message: "Hello, h3!" };
})
);
app.use(router);
const server = createServer(toNodeHandler(app));
server.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});
Web 標準(Fetch API)で起動する場合(Bun / Deno / Cloudflare Workers)
import { createApp, createRouter, defineEventHandler, toWebHandler } from "h3";
const app = createApp();
const router = createRouter();
router.get(
"/",
defineEventHandler(() => {
return { message: "Hello from the edge!" };
})
);
app.use(router);
const handler = toWebHandler(app);
// Bun の場合
export default { port: 3000, fetch: handler };
// Deno の場合
// Deno.serve({ port: 3000 }, handler);
よく使う API
1. readBody — リクエストボディの取得
import { defineEventHandler, readBody } from "h3";
router.post(
"/users",
defineEventHandler(async (event) => {
const body = await readBody<{ name: string; email: string }>(event);
return { created: true, user: body };
})
);
2. getQuery — クエリパラメータの取得
import { defineEventHandler, getQuery } from "h3";
router.get(
"/search",
defineEventHandler((event) => {
const query = getQuery<{ q: string; page?: string }>(event);
return { keyword: query.q, page: Number(query.page) || 1 };
})
);
3. getRouterParam / getRouterParams — パスパラメータの取得
import { defineEventHandler, getRouterParam, getRouterParams } from "h3";
router.get(
"/users/:userId/posts/:postId",
defineEventHandler((event) => {
// 個別に取得
const userId = getRouterParam(event, "userId");
// まとめて取得
const params = getRouterParams(event);
// => { userId: "123", postId: "456" }
return { userId, postId: params.postId };
})
);
4. setResponseHeader / setResponseStatus — レスポンスの制御
import {
defineEventHandler,
setResponseHeader,
setResponseStatus,
} from "h3";
router.post(
"/items",
defineEventHandler(async (event) => {
const body = await readBody(event);
setResponseStatus(event, 201);
setResponseHeader(event, "X-Custom-Header", "my-value");
return { created: true, item: body };
})
);
5. createError — エラーハンドリング
import { createError, defineEventHandler, getRouterParam } from "h3";
router.get(
"/users/:id",
defineEventHandler((event) => {
const id = getRouterParam(event, "id");
if (!id || isNaN(Number(id))) {
throw createError({
statusCode: 400,
statusMessage: "Bad Request",
message: "Invalid user ID",
});
}
// ユーザーが見つからない場合
const user = findUserById(Number(id));
if (!user) {
throw createError({
statusCode: 404,
statusMessage: "Not Found",
message: `User ${id} not found`,
});
}
return user;
})
);
補足: ミドルウェアの定義
import { defineEventHandler, getRequestHeader } from "h3";
// ミドルウェアとして登録(パスなしで app.use に渡す)
app.use(
defineEventHandler((event) => {
const auth = getRequestHeader(event, "authorization");
if (!auth) {
throw createError({ statusCode: 401, message: "Unauthorized" });
}
// 何も返さなければ次のハンドラに処理が渡る
})
);
類似パッケージとの比較
| 特徴 | h3 | Express | Hono | Fastify |
|---|---|---|---|---|
| ランタイム | Node / Bun / Deno / Edge | Node 中心 | Node / Bun / Deno / Edge | Node 中心 |
| Web 標準準拠 | ✅(v2 で強化) | ❌ | ✅ | ❌ |
| TypeScript サポート | ✅ ネイティブ | △(@types 必要) | ✅ ネイティブ | ✅ ネイティブ |
| バンドルサイズ | 極小 | 中 | 極小 | 中 |
| エコシステム | UnJS / Nuxt / Nitro | 最大規模 | 急成長中 | プラグイン豊富 |
| ミドルウェア互換 | Node.js ミドルウェアをアダプタ経由で利用可 | Express ミドルウェア | 独自 | Fastify プラグイン |
| 主な用途 | Nuxt サーバー / エッジ API | 汎用 Web サーバー | エッジ API / フルスタック | 高性能 REST API |
選定の目安:
- Nuxt / Nitro を使うなら → h3(必然的に使うことになる)
- エッジファーストで Hono と迷うなら → API スタイルの好みで選んで OK。h3 は UnJS エコシステムとの統合が強み
- 既存の Express 資産が多いなら → Express のままか Fastify への移行を検討
注意点・Tips
v1 → v2 の破壊的変更に注意
h3 v2 では多くの API がリネーム・再設計されています。例えば:
useBody()→readBody()useQuery()→getQuery()sendError()→throw createError()に統一
v2 はまだ RC(リリース候補)段階です(2.0.1-rc.20 時点)。プロダクション導入時はバージョンを固定し、CHANGELOG を追跡してください。
ハンドラの戻り値がそのままレスポンスになる
h3 では defineEventHandler の戻り値が自動的にレスポンスボディになります。オブジェクトを返せば application/json として、文字列を返せば text/plain としてシリアライズされます。明示的に res.json() を呼ぶ必要はありません。
// JSON レスポンス(自動)
defineEventHandler(() => ({ ok: true }));
// テキストレスポンス(自動)
defineEventHandler(() => "Hello!");
// null を返すと 204 No Content
defineEventHandler(() => null);
event オブジェクトを常に引き回す
Express の req / res と異なり、h3 では統一された event オブジェクトを各ユーティリティ関数に渡す設計です。これにより、ランタイムに依存しない抽象化が実現されています。
// ✅ Good: ユーティリティ関数に event を渡す
const body = await readBody(event);
const query = getQuery(event);
// ❌ Bad: Node.js 固有の API に直接アクセスしない
// event.node.req は Node.js 環境でのみ動作する
パフォーマンス Tips
- ルーターは
createRouter()で作成し、app.use(router)で登録するのが推奨パターンです。内部で radix tree ベースのルーティング(radix3)が使われており、ルート数が増えても高速にマッチングされます。 - 静的レスポンスはハンドラ外でキャッシュし、ハンドラ内ではオブジェクト生成を最小限にすると、GC 負荷を抑えられます。
まとめ
h3 は「どこでも動く HTTP サーバー」を最小限のコードで実現するフレームワークです。Nuxt 3 / Nitro の基盤として実戦投入されており、信頼性は十分に検証されています。v2 で Web 標準への準拠がさらに強化され、エッジコンピューティング時代の HTTP フレームワークとして有力な選択肢です。Express に慣れたエンジニアでも、event ベースの設計に慣れれば移行はスムーズでしょう。