hono について
Honoは、複数のJavaScriptランタイムで動作するルーティング・ミドルウェアフレームワークです。JSXレンダリング、ストリーミング、型付きRPCクライアントも備えています。
主な機能
- 01ルーティングとミドルウェア
- 02複数ランタイム対応
- 03型付きRPCクライアント
- 04JSXレンダリングとストリーミング
活用シーン
- 01複数ランタイム向けのAPI構築
- 02認証とリクエスト検証の追加
- 03型付きAPIクライアントの作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ terminalskills/ skills --skill honoインストール後、Claude Code で/honoを入力して読み込まれたか確認します。
同梱ファイル_scores.json
詳細
Hono — 超高速Webフレームワーク
概要
Hono(hono 4.x、4.13で確認済み)は、Request/Response のみを基盤とするルーターとミドルウェアで構成されているため、同じアプリオブジェクトを Workers、Bun、Deno、Node.js、Lambda、Vercel で実行できます。コアパッケージには、独自のミドルウェア(hono/cors、hono/jwt、hono/logger、hono/csrf、hono/secure-headers、hono/body-limit、hono/compress、hono/etag、hono/timeout、hono/request-id など)、JSX レンダラー(hono/jsx)、型付き RPC クライアント(hono/client)が含まれています。バリデーターなどのサードパーティ製ミドルウェアは、@hono/* パッケージで提供されています。組み込みのレートリミッターや ORM はありません。
手順
プロジェクトを作成する
npm create hono@latest orders-api
# pick a template: cloudflare-workers, nodejs, bun, deno, aws-lambda, vercel, ...
cd orders-api && npm install && npm run dev
既存の Node.js プロジェクトでは hono と @hono/node-server をインストールし、serve({ fetch: app.fetch, port: 3000, hostname: "127.0.0.1" }) でアプリを起動します。Workers、Bun、Deno では、代わりに export default app を使用します。
バリデーションと JWT を使った API ルート
バリデーターは別途インストールします:npm install @hono/zod-validator zod。
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { jwt, sign } from "hono/jwt";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
type Variables = { jwtPayload: { sub: string } };
const app = new Hono<{ Variables: Variables }>();
app.use(logger());
app.use("/api/*", cors({ origin: ["https://brightcart.io"], credentials: true }));
// `alg` is required: the middleware throws without it
app.use("/api/protected/*", jwt({ secret: process.env.JWT_SECRET!, alg: "HS256" }));
const createUser = z.object({
name: z.string().min(1),
email: z.email(), // zod 4; on zod 3 use z.string().email()
role: z.enum(["user", "admin"]).default("user"),
});
const routes = app
.post("/api/users", zValidator("json", createUser), (c) => {
const body = c.req.valid("json"); // { name: string; email: string; role: "user" | "admin" }
return c.json(body, 201);
})
.get("/api/protected/me", (c) => c.json({ userId: c.get("jwtPayload").sub }));
export type AppType = typeof routes;
export default app;
スキーマに適合しないボディには、自動的に 400 の JSON レスポンスが返されます。(上記のように)ルートをチェーンすると、typeof routes にすべてのルート型が引き継がれます。app.get(...) のように個別に登録したルートは、その型に含まれません。トークンは await sign({ sub: user.id, exp: Math.floor(Date.now() / 1000) + 3600 }, secret, "HS256") で発行します(sign は非同期で、3 番目の引数としてアルゴリズムを指定します)。
パスは c.req.param("id")、クエリは c.req.query("page")、ヘッダーは c.req.header("x-request-id") で取得します。サブアプリをマウントしてルートをグループ化します:app.route("/api/orders", ordersApp)。
エラー
import { HTTPException } from "hono/http-exception";
app.notFound((c) => c.json({ error: "Not found" }, 404));
app.onError((err, c) => {
// JWT, bearer-auth and other middleware throw HTTPException: keep its status
if (err instanceof HTTPException) return err.getResponse();
console.error(err);
return c.json({ error: "Internal server error" }, 500);
});
HTTPException の分岐がない場合、トークンがないと 401 ではなく 500 が返されます。
RPC クライアント
import { hc } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("https://api.brightcart.io");
const res = await client.api.users.$post({ json: { name: "Alice", email: "[email protected]" } });
if (res.ok) console.log(await res.json());
クライアントコードでは、サーバーファイルから import type のみをインポートします。動的セグメントは client.api.users[":id"].$get({ param: { id: "42" } }) のように呼び出します。
JSX、HTML、ストリーミング
JSX を使うには、tsconfig.json で "jsx": "react-jsx" と "jsxImportSource": "hono/jsx" を設定し、ファイルの拡張子を .tsx にする必要があります(Workers のテンプレートでは、すでに設定されています)。
import { getRuntimeKey } from "hono/adapter";
import { streamText } from "hono/streaming";
app.get("/", (c) =>
c.html(
<html>
<body>
<h1>Order dashboard</h1>
<p>Runtime: {getRuntimeKey()}</p>
</body>
</html>
)
);
app.get("/stream", (c) =>
streamText(c, async (stream) => {
for (const word of ["Preparing", "your", "report"]) {
await stream.write(word + " ");
await stream.sleep(100);
}
})
);
streamText、stream、streamSSE は hono/streaming からインポートします。4.x では、以前の c.streamText() コンテキストメソッドは廃止されています。hono/adapter の getRuntimeKey() を使って、実行環境("node"、"workerd"、"bun"、"deno")を判定します。
例
例1:「Hono API に保護されたエンドポイントを追加する」
リクエスト:「ログイン済みのユーザーだけが GET /api/protected/me を呼び出せるようにしてください。」
手順:示されているように、/api/protected/* に jwt({ secret: process.env.JWT_SECRET!, alg: "HS256" }) を追加し、c.get("jwtPayload") を読み取ります。ヘッダーのないリクエストには(上記の onError 分岐があれば)401 が返され、有効な Authorization: Bearer <token> があるリクエストには {"userId":"u1"} が返されます。
例2:「サインアップのボディを検証する」
リクエスト:「名前が空のサインアップを拒否してください。」
手順:ルートに zValidator("json", createUser) を追加します。{"name":"Alice","email":"[email protected]"} を指定した POST /api/users には、デフォルト値で "role":"user" が補完された 201 が返されます。{"name":""} には、Zod の問題を含む 400 が返されます。
ガイドライン
jwt()では常にalgを設定し、シークレットは環境変数に保存します(Workers では、ラッパーミドルウェア内でc.env.JWT_SECRETから読み取ります)。@hono/zod-validatorは、使用する Zod のメジャーバージョンに合ったバージョンに固定してください。メジャーバージョンが一致しないと型が壊れます。- ミドルウェアの順序は重要です。適用対象のルートより前に
cors()を登録し、Responseを返すハンドラーはチェーンを終了します。 - ブラウザー向けアプリには
body-limit、secure-headers、csrfを追加します。レート制限にはサードパーティ製パッケージか、プラットフォーム(Cloudflare のルール)が必要です。 - 外部公開する意図がない限り、ローカルの Node サーバーは
127.0.0.1にバインドします。 - Node 専用フレームワークのプラグインエコシステムに依存するアプリには Hono を使わないでください(Express のミドルウェアはそのままでは動作しません)。