mcpField
H
hono

hono

github.com/terminalskills開発19376 回インストール3 日前に更新
リポジトリ

hono について

Honoは、複数のJavaScriptランタイムで動作するルーティング・ミドルウェアフレームワークです。JSXレンダリング、ストリーミング、型付きRPCクライアントも備えています。

主な機能

  1. 01ルーティングとミドルウェア
  2. 02複数ランタイム対応
  3. 03型付きRPCクライアント
  4. 04JSXレンダリングとストリーミング

活用シーン

  1. 01複数ランタイム向けのAPI構築
  2. 02認証とリクエスト検証の追加
  3. 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 のミドルウェアはそのままでは動作しません)。

コメント