mcpField
H
hono

hono

github.com/sickn33開発49.1k60 回インストール今日更新
リポジトリ

hono について

Honoは、エッジ API、BFF、フルスタックアプリを構築する開発者向けの軽量 Web フレームワークです。Web 標準に基づき、複数のランタイムでルーティング、ミドルウェア、型付き RPC クライアントを利用できます。

主な機能

  1. 01Web 標準ベースのリクエストとレスポンス
  2. 02複数の JavaScript ランタイムに対応
  3. 03ルーティングとミドルウェア
  4. 04組み込み JSX サポート
  5. 05型付き RPC クライアント

活用シーン

  1. 01エッジ REST API または RPC API の構築
  2. 02Bun または Node.js での型付きサービス開発
  3. 03低レイテンシ BFF の開発
  4. 04フロントエンドとバックエンドでの型付きルート共有

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill hono

インストール後、Claude Code で/honoを入力して読み込まれたか確認します。

詳細

Hono Web フレームワーク

概要

Hono(炎、日本語で「flame」)は、Web 標準(Request/Response/fetch)を基盤とする、小型で超高速な Web フレームワークです。Cloudflare Workers、Deno Deploy、Bun、Node.js、AWS Lambda、および WinterCG 互換ランタイムならどこでも、同じコードで動作します。Hono のルーターは利用可能なものの中でも特に高速で、ミドルウェアシステム、組み込みの JSX サポート、RPC クライアントを備えているため、エッジ API、BFF、軽量なフルスタックアプリに適しています。

このスキルを使う場面

  • エッジへのデプロイ(Cloudflare Workers、Deno Deploy)向けに REST API または RPC API を構築する場合
  • Bun または Node.js 用に、最小限でありながら型安全なサーバーフレームワークが必要な場合
  • 低レイテンシ要件を満たすバックエンド・フォー・フロントエンド(BFF)層を構築する場合
  • Express から移行しつつ、TypeScript のサポート強化とエッジ互換性を求める場合
  • ユーザーが Hono のルーティング、ミドルウェア、c.req、c.json、または hc() RPC クライアントについて質問した場合

動作方法

ステップ 1:プロジェクトのセットアップ

Cloudflare Workers(エッジ向けに推奨):

npm create hono@latest my-api
# Select: cloudflare-workers
cd my-api
npm install
npm run dev    # Wrangler local dev
npm run deploy # Deploy to Cloudflare

Bun/Node.js:

mkdir my-api && cd my-api
bun init
bun add hono
// src/index.ts (Bun)
import { Hono } from 'hono';

const app = new Hono();

app.get('/', c => c.text('Hello Hono!'));

export default {
  port: 3000,
  fetch: app.fetch,
};
ステップ 2:ルーティング
import { Hono } from 'hono';

const app = new Hono();

// Basic methods
app.get('/posts', c => c.json({ posts: [] }));
app.post('/posts', c => c.json({ created: true }, 201));
app.put('/posts/:id', c => c.json({ updated: true }));
app.delete('/posts/:id', c => c.json({ deleted: true }));

// Route params and query strings
app.get('/posts/:id', async c => {
  const id = c.req.param('id');
  const format = c.req.query('format') ?? 'json';
  return c.json({ id, format });
});

// Wildcard
app.get('/static/*', c => c.text('static file'));

export default app;

チェーン形式のルーティング:

app
  .get('/users', listUsers)
  .post('/users', createUser)
  .get('/users/:id', getUser)
  .patch('/users/:id', updateUser)
  .delete('/users/:id', deleteUser);
ステップ 3:ミドルウェア

Hono のミドルウェアは fetch インターセプターとまったく同じように、ハンドラーの前後で動作します。

import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { bearerAuth } from 'hono/bearer-auth';

const app = new Hono<{ Variables: { requestId: string } }>();

// Built-in middleware
app.use('*', logger());
app.use('/api/*', cors({ origin: 'https://myapp.com' }));
app.use('/api/admin/*', bearerAuth({ token: process.env.API_TOKEN! }));

// Custom middleware
app.use('*', async (c, next) => {
  c.set('requestId', crypto.randomUUID());
  await next();
  c.header('X-Request-Id', c.get('requestId'));
});

利用可能な組み込みミドルウェア: logger、cors、csrf、etag、cache、basicAuth、bearerAuth、jwt、compress、bodyLimit、timeout、prettyJSON、secureHeaders。

以下は、順番に実行するハンドラーではなく、代替の解析/レスポンス方法を示しています。Content-Type に応じてボディを一度だけ解析し、実行時に検証して、レスポンスを必ず 1 つだけ返してください。hono/cookie から getCookie をインポートしてください。JSON のジェネリクスでは入力を検証できません。D1 と変更リクエストを検証/認可し、ボディサイズを制限し、承認済みの公開フィールドのみを返して、エラーを処理してください。Bearer/JWT のスニペットでは、設定済みの強力なシークレットとアルゴリズム、およびデプロイ環境に応じたクレーム/有効期限/オーディエンス/発行者のチェックが必要です。インストール済みの Hono のメジャーバージョンで正確な API を確認してください。Workers では、Node の process.env を使う例ではなく、c.env バインディングを使用してください。CORS は認可の代わりにはなりません。スキャフォールディングのデプロイコマンドは稼働中のリソースを公開するため、承認済みのアカウント/ルートが必要です。

ステップ 4:リクエストとレスポンスのヘルパー
app.post('/submit', async c => {
  // Parse body
  const body = await c.req.json<{ name: string; email: string }>();
  const form = await c.req.formData();
  const text = await c.req.text();

  // Headers and cookies
  const auth = c.req.header('authorization');
  const token = getCookie(c, 'session');

  // Responses
  return c.json({ ok: true });                        // JSON
  return c.text('hello');                             // plain text
  return c.html('<h1>Hello</h1>');                    // HTML
  return c.redirect('/dashboard', 302);              // redirect
  return new Response(stream, { status: 200 });       // raw Response
});
ステップ 5:Zod バリデーターのミドルウェア
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const createPostSchema = z.object({
  title: z.string().min(1).max(200),
  body: z.string().min(1),
  tags: z.array(z.string()).default([]),
});

app.post(
  '/posts',
  zValidator('json', createPostSchema),
  async c => {
    const data = c.req.valid('json'); // fully typed
    const post = await db.post.create({ data });
    return c.json(post, 201);
  }
);
ステップ 6:ルートグループとアプリの構成
// src/routes/posts.ts
import { Hono } from 'hono';

const posts = new Hono();

posts.get('/', async c => { /* list posts */ });
posts.post('/', async c => { /* create post */ });
posts.get('/:id', async c => { /* get post */ });

export default posts;
// src/index.ts
import { Hono } from 'hono';
import posts from './routes/posts';
import users from './routes/users';

const app = new Hono().basePath('/api');

app.route('/posts', posts);
app.route('/users', users);

export default app;
ステップ 7:RPC クライアント(エンドツーエンドの型安全性)

Hono の RPC モードでは、ルートの型をエクスポートし、hc クライアントが利用します。これは tRPC に似ていますが、fetch の規約を使用します。

// server: src/routes/posts.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const posts = new Hono()
  .get('/', c => c.json({ posts: [{ id: '1', title: 'Hello' }] }))
  .post(
    '/',
    zValidator('json', z.object({ title: z.string() })),
    async c => {
      const { title } = c.req.valid('json');
      return c.json({ id: '2', title }, 201);
    }
  );

export default posts;
export type PostsType = typeof posts;
// client: src/client.ts
import { hc } from 'hono/client';
import type { PostsType } from '../server/routes/posts';

const client = hc<PostsType>('/api/posts');

// Fully typed — autocomplete on routes, params, and responses
const response = await client.$get();
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { posts } = await response.json();
const created = await client.$post({ json: { title: 'New Post' } });
const newPost = await created.json();

例

例 1:JWT 認証ミドルウェア
import { Hono } from 'hono';
import { jwt, sign } from 'hono/jwt';

const app = new Hono();
const SECRET = process.env.JWT_SECRET!;

app.post('/login', async c => {
  const { email, password } = await c.req.json();
  const user = await validateUser(email, password);
  if (!user) return c.json({ error: 'Invalid credentials' }, 401);

  const token = await sign({ sub: user.id, exp: Math.floor(Date.now() / 1000) + 3600 }, SECRET);
  return c.json({ token });
});

app.use('/api/*', jwt({ secret: SECRET }));
app.get('/api/me', async c => {
  const payload = c.get('jwtPayload');
  const user = await getUserById(payload.sub);
  return c.json(user);
});

export default app;
例 2:D1 データベースを使用する Cloudflare Workers
// src/index.ts
import { Hono } from 'hono';

type Bindings = {
  DB: D1Database;
  API_TOKEN: string;
};

const app = new Hono<{ Bindings: Bindings }>();

app.get('/users', async c => {
  const { results } = await c.env.DB.prepare('SELECT * FROM users LIMIT 50').all();
  return c.json(results);
});

app.post('/users', async c => {
  const { name, email } = await c.req.json();
  await c.env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
    .bind(name, email)
    .run();
  return c.json({ created: true }, 201);
});

export default app;
例 3:ストリーミングレスポンス
import { stream, streamText } from 'hono/streaming';

app.get('/stream', c =>
  streamText(c, async stream => {
    for (const chunk of ['Hello', ' ', 'World']) {
      await stream.write(chunk);
      await stream.sleep(100);
    }
  })
);

ベストプラクティス

  • ✅ ハンドラーを別々のファイルに分けるには、ルートグループ(サブアプリ)を使用します。app.route('/users', usersRouter)
  • ✅ すべてのリクエストボディ、クエリ、パラメーターの検証に zValidator を使用します
  • ✅ Bindings ジェネリクスを使って Cloudflare Workers のバインディングに型を付けます。new Hono<{ Bindings: Env }>()
  • ✅ フロントエンドとバックエンドが同じリポジトリを共有している場合は、RPC クライアント(hc)を使用します
  • ✅ コードを簡潔にするため、new Response() より c.json()/c.text() を優先します
  • ❌ エッジへの移植性が必要な場合は、Node.js 固有の API(fs、path、process)を使用しないでください
  • ❌ 重い依存関係を追加しないでください。Hono の強みは、エッジランタイムでの小さなフットプリントです
  • ❌ ミドルウェアの型付けを省略しないでください。c.get() の型安全性を保つため、ジェネリクス(Variables、Bindings)を使用します

セキュリティと安全に関する注意事項

  • リクエストのデータを使用する前に、必ず zValidator で入力を検証してください。
  • HTML/フォームを提供する場合は、変更エンドポイントで Hono 組み込みの csrf ミドルウェアを使用してください。
  • Cloudflare Workers では、機密情報ではない設定を wrangler.toml [vars] に保存し、シークレットは wrangler secret put で保存してください。ソースコードに直接記述してはいけません。
  • bearerAuth または jwt を使用する場合は、トークンがサーバー側で検証されることを確認してください。クライアントから提供されたユーザー ID を信頼してはいけません。
  • 機密性の高いエンドポイント(認証、パスワードリセット)には、Cloudflare Rate Limiting またはカスタムミドルウェアでレート制限を設けてください。

よくある落とし穴

  • 問題: ハンドラーが undefined を返すため、レスポンスが空になる 解決策: ハンドラーからは必ずレスポンスを return してください。c.json(...) だけではなく、return c.json(...) とします。

  • 問題: レスポンスが送信された後にミドルウェアが実行される 解決策: レスポンス後の処理の前に await next() を呼び出してください。Hono は、レスポンスがチェーンを戻る際に next() の後のコードを実行します。

  • 問題: Node.js で c.env が undefined になる 解決策: Cloudflare の env バインディングは Workers でのみ使用できます。Node.js では process.env を使用してください。

  • 問題: ルートが一致せず、404 が返る 解決策: app.route('/prefix', subRouter) が、クライアントから呼び出すプレフィックスと同じものを使用していることを確認してください。サブルーターの各ルートでプレフィックスを繰り返してはいけません。

関連スキル

  • @cloudflare-workers-expert — Cloudflare Workers プラットフォーム固有の詳細
  • @trpc-fullstack — TypeScript のフルスタックアプリ向けの代替 RPC アプローチ
  • @zod-validation-expert — @hono/zod-validator で使用する Zod スキーマのパターンの詳細
  • @nodejs-backend-patterns — Node.js 固有のバックエンドが必要な場合(エッジではない環境)

制限事項

  • このスキルは、タスクが上記の説明された範囲に明確に当てはまる場合にのみ使用してください。
  • 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
  • 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認を求めてください。

コメント