はじめに
「Nuxtのサーバー機能って、裏側でどう動いているんだろう?」「Cloudflare WorkersでもNode.jsでも同じコードでAPIを動かしたい」——そんな疑問や要望を持ったことはありませんか。
実はNuxtのサーバーエンジンNitroの内部で、リクエスト処理の中核を担っているのが今回紹介するH3です。単体のWebフレームワークとしても十分に強力で、Web標準に忠実な設計のおかげでどんな実行環境でも同じ書き方でサーバーを構築できます。この記事では、H3の特徴からインストール方法、基本的なルーティング、実践的な使い方までを順を追って解説します。
H3とは
H3(読み方は「エイチスリー」)は、unjsエコシステムが開発している、高パフォーマンスと移植性を追求した最小構成のHTTPフレームワークです。GitHub上で5,000スターを超える人気を集めており、公式サイトはh3.devで公開されています。
最大の特徴は、NuxtのサーバーエンジンであるNitroの内部で採用されている点です。つまりNuxtアプリのserver/apiディレクトリで書いているサーバーコードは、実質的にH3の上で動いています。とはいえH3自体はNuxt専用ではなく、単体のパッケージとしてnpm install h3だけで導入し、独立したAPIサーバーとして使うことも可能です。
主な特徴
- Web標準ベースの設計 - 標準の
Request/Responseオブジェクトを基盤にしているため、Node.js固有のAPIに依存しません - マルチランタイム対応 - Node.js、Deno、Bun、Cloudflare Workersなど、同じコードで様々な実行環境に対応できます
- 軽量かつ高性能 - 最小限の依存関係で構成されており、ルーティング処理も高速に動作します
- 柔軟なルーティング - 名前付きパラメータやワイルドカードを使った直感的なルート定義が可能です
- ミドルウェア・プラグイン機構 - リクエストやレスポンスへの割り込み処理、再利用可能なロジックの拡張が容易です
- WebSocket / SSE対応 - クロスプラットフォームでWebSocketやServer-Sent Eventsを扱えます
インストール
まずはプロジェクトにH3を追加しましょう。お使いのパッケージマネージャーでインストールできます。
# npm
npm install h3
# yarn
yarn add h3
# pnpm
pnpm add h3
基本的な使い方
もっともシンプルなサーバーは、次のようにわずか数行で書けます。
// server.mjs
import { H3, serve } from "h3";
const app = new H3().get("/", () => "⚡️ Tadaa!");
serve(app, { port: 3000 });
実行方法もランタイムごとに一行で済みます。
# Node.js
node --watch ./server.mjs
# Deno
deno run -A --watch ./server.mjs
# Bun
bun run --watch server.mjs
ルーティング
HTTPメソッドごとにハンドラを登録できます。
const app = new H3();
app.get("/hello", () => "Hello world!");
app.post("/hello", () => "POST Hello world!");
app.all("/hello", () => "Any other method!");
URLパラメータは:nameのような名前付きパラメータで受け取れます。
app.get("/hello/:name", (event) => {
return `Hello, ${event.context.params.name}!`;
});
パス以下をまるごと受け取りたい場合は、ワイルドカードパラメータ**が便利です。
app.get("/files/**", (event) => {
return `Requested path: ${event.context.params._}`;
});
ミドルウェア
use()でリクエストの前後に処理を挟み込めます。例えば簡易的なロギングミドルウェアは次のように書けます。
app.use((event) => {
console.log(`${event.req.method} ${event.url.pathname}`);
});
JSONレスポンスとエラーハンドリング
イベントハンドラの返り値はオブジェクトでも構いません。自動的にJSONへシリアライズされます。
import { H3, HTTPError } from "h3";
const app = new H3();
app.get("/users/:id", (event) => {
const id = event.context.params.id;
if (!/^\d+$/.test(id)) {
throw new HTTPError({
status: 400,
statusText: "Invalid user id",
});
}
return { id, name: "Sample User" };
});
HTTPErrorを投げるだけで、適切なステータスコードとエラーレスポンスが自動的に返される点が便利です。
実践的なユースケース
軽量なREST APIサーバー
小規模なREST APIを構築する場合、Expressのような重量級フレームワークを使わずとも、H3だけで必要十分な機能をまかなえます。
import { H3, serve } from "h3";
const todos = [{ id: 1, title: "H3を試す" }];
const app = new H3();
app.get("/todos", () => todos);
app.post("/todos", async (event) => {
const body = await event.req.json();
const todo = { id: todos.length + 1, title: body.title };
todos.push(todo);
return todo;
});
serve(app, { port: 3000 });
Cloudflare Workersへのデプロイ
app.fetchをそのままエクスポートするだけで、Cloudflare Workersのようなエッジ環境でも動作します。Node.js向けに書いたルーティングロジックを、そのままエッジへ持っていける点は大きな強みです。
// worker.mjs
import { H3 } from "h3";
const app = new H3().get("/", () => "Hello from the edge!");
export default {
fetch: app.fetch,
};
ネストしたアプリでの機能分割
APIの規模が大きくなってきたら、サブアプリをメインアプリにマウントして責務を分割できます。
const userApp = new H3().get("/", () => "User list");
const app = new H3().mount("/users", userApp);
こうした設計により、機能ごとにファイルを分割しながらも一つのサーバーとして統合できます。
まとめ
H3は、Web標準に忠実でありながら軽量・高性能を実現したHTTPフレームワークです。Nuxtのサーバーエンジンとして採用されている実績が示す通り、実運用に耐える堅牢さを持ちつつ、シンプルなAPIで学習コストも低く抑えられています。
Node.jsだけでなくDeno、Bun、Cloudflare Workersまで同じコードで動かせる移植性は、マルチランタイム時代のバックエンド開発において大きな武器になります。まずはnpm install h3で試しに小さなAPIを組んでみて、その手軽さと拡張性を体感してみてはいかがでしょうか。