hono-rpc
Honoフレームワークで型安全なREST APIを構築する際に、フロントエンドとバックエンドで型を共有したり、tRPCをREST互換に置き換えたりする際に役立つ、エンドツーエンドで型安全なAPIクライアントを構築するSkill。
📜 元の英語説明(参考)
Hono RPC — end-to-end type-safe API client for the Hono web framework. Use when building type-safe REST APIs with Hono, sharing types between frontend and backend, or replacing tRPC with a REST-compatible approach. Works on Cloudflare Workers, Deno, Bun, and Node.js.
🇯🇵 日本人クリエイター向け解説
Honoフレームワークで型安全なREST APIを構築する際に、フロントエンドとバックエンドで型を共有したり、tRPCをREST互換に置き換えたりする際に役立つ、エンドツーエンドで型安全なAPIクライアントを構築するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o hono-rpc.zip https://jpskill.com/download/14982.zip && unzip -o hono-rpc.zip && rm hono-rpc.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/14982.zip -OutFile "$d\hono-rpc.zip"; Expand-Archive "$d\hono-rpc.zip" -DestinationPath $d -Force; ri "$d\hono-rpc.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
hono-rpc.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
hono-rpcフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Hono RPC
概要
Hono RPC は、Hono サーバーとそのクライアント間のエンドツーエンドの型安全性を実現します。サーバー上で型付きバリデーターを使用してルートを定義し、AppType をエクスポートし、hc クライアントを使用して、フロントエンドまたは別のサービスから完全に型安全な HTTP 呼び出しを行います。コード生成もスキーマファイルも不要です。
インストール
# npm / Node.js
npm install hono
npm install zod @hono/zod-validator
# Bun
bun add hono zod @hono/zod-validator
# Deno (deno.json imports)
# "hono": "jsr:@hono/hono@^4"
サーバーの設定
基本的なルート定義
// src/server.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono();
// 型付きレスポンスでルートを定義します
const route = app.get("/hello", (c) => {
return c.json({ message: "Hello, Hono!" });
});
export type AppType = typeof route;
export default app;
複数のルートを持つ完全な API
// src/server.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
// スキーマ
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
const updateUserSchema = createUserSchema.partial();
// ルーター
const users = new Hono()
.get("/", (c) => {
const users = [{ id: 1, name: "Alice", email: "alice@example.com" }];
return c.json({ users });
})
.get("/:id", (c) => {
const id = Number(c.req.param("id"));
return c.json({ user: { id, name: "Alice", email: "alice@example.com" } });
})
.post(
"/",
zValidator("json", createUserSchema),
async (c) => {
const data = c.req.valid("json"); // 完全に型付けされています
const user = { id: Date.now(), ...data };
return c.json({ user }, 201);
}
)
.patch(
"/:id",
zValidator("json", updateUserSchema),
async (c) => {
const id = Number(c.req.param("id"));
const data = c.req.valid("json");
return c.json({ user: { id, ...data } });
}
)
.delete("/:id", (c) => {
const id = Number(c.req.param("id"));
return c.json({ deleted: id });
});
const app = new Hono().route("/users", users);
// 型をエクスポートします — これが Hono RPC の鍵です
export type AppType = typeof app;
export default app;
クライアントの使用法
// src/client.ts
import { hc } from "hono/client";
import type { AppType } from "./server";
// 型付きクライアントを作成します
const client = hc<AppType>("http://localhost:3000");
// すべての呼び出しは完全に型安全です
async function main() {
// GET /users — レスポンスは型付けされています
const res = await client.users.$get();
const { users } = await res.json(); // users: { id: number; name: string; email: string }[]
// POST /users — リクエストボディは型チェックされます
const createRes = await client.users.$post({
json: {
name: "Bob",
email: "bob@example.com",
age: 30,
},
});
const { user } = await createRes.json();
// GET /users/:id — 型付きパラメーター
const userRes = await client.users[":id"].$get({ param: { id: "1" } });
const data = await userRes.json();
// PATCH /users/:id
await client.users[":id"].$patch({
param: { id: "1" },
json: { name: "Bob Updated" },
});
// DELETE /users/:id
await client.users[":id"].$delete({ param: { id: "1" } });
}
クエリパラメーター
// サーバー — クエリスキーマを定義します
const searchSchema = z.object({
q: z.string().optional(),
page: z.coerce.number().default(1),
limit: z.coerce.number().default(20),
});
const app = new Hono().get(
"/search",
zValidator("query", searchSchema),
(c) => {
const { q, page, limit } = c.req.valid("query");
return c.json({ results: [], q, page, limit });
}
);
export type AppType = typeof app;
// クライアント
const client = hc<AppType>("http://localhost:3000");
const res = await client.search.$get({
query: { q: "hono", page: "1", limit: "10" },
});
ヘッダーと認証
// クライアントファクトリーでヘッダーを渡します
const client = hc<AppType>("http://localhost:3000", {
headers: {
Authorization: `Bearer ${token}`,
},
});
// またはリクエストごとに
const res = await client.users.$get(undefined, {
headers: { "X-Request-ID": crypto.randomUUID() },
});
Cloudflare Workers へのデプロイ
// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono<{ Bindings: CloudflareBindings }>();
// ミドルウェア
app.use("*", logger());
app.use(
"/api/*",
cors({ origin: ["https://myapp.com"], allowMethods: ["GET", "POST", "PUT", "DELETE"] })
);
// D1 データベースバインディングを使用したルート
const api = app
.get("/api/items", async (c) => {
const db = c.env.DB; // Cloudflare D1
const items = await db.prepare("SELECT * FROM items").all();
return c.json({ items: items.results });
})
.post(
"/api/items",
zValidator("json", z.object({ name: z.string(), price: z.number() })),
async (c) => {
const { name, price } = c.req.valid("json");
const db = c.env.DB;
await db.prepare("INSERT INTO items (name, price) VALUES (?, ?)").bind(name, price).run();
return c.json({ ok: true }, 201);
}
);
export type AppType = typeof api;
export default app;
# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "your-database-id"
フロントエンドとの型の共有
// packages/api/src/types.ts — 共有パッケージ
export type { AppType } from "./server";
// packages/web/src/api.ts — フロントエンド
import { hc } from "hono/client";
import type { AppType } from "@myapp/api";
export const api = hc<AppType>(import.meta.env.VITE_API_URL);
// React コンポーネント — 完全に型付けされています
const { data } = useQuery({
queryKey: ["users"],
queryFn: async () => {
co
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Hono RPC
Overview
Hono RPC provides end-to-end type safety between a Hono server and its clients. Define routes with typed validators on the server, export the AppType, and use the hc client to make fully type-safe HTTP calls from the frontend or another service — no code generation, no schema files.
Installation
# npm / Node.js
npm install hono
npm install zod @hono/zod-validator
# Bun
bun add hono zod @hono/zod-validator
# Deno (deno.json imports)
# "hono": "jsr:@hono/hono@^4"
Server Setup
Basic Route Definition
// src/server.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono();
// Define a route with typed response
const route = app.get("/hello", (c) => {
return c.json({ message: "Hello, Hono!" });
});
export type AppType = typeof route;
export default app;
Full API with Multiple Routes
// src/server.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
// Schemas
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
const updateUserSchema = createUserSchema.partial();
// Router
const users = new Hono()
.get("/", (c) => {
const users = [{ id: 1, name: "Alice", email: "alice@example.com" }];
return c.json({ users });
})
.get("/:id", (c) => {
const id = Number(c.req.param("id"));
return c.json({ user: { id, name: "Alice", email: "alice@example.com" } });
})
.post(
"/",
zValidator("json", createUserSchema),
async (c) => {
const data = c.req.valid("json"); // Fully typed
const user = { id: Date.now(), ...data };
return c.json({ user }, 201);
}
)
.patch(
"/:id",
zValidator("json", updateUserSchema),
async (c) => {
const id = Number(c.req.param("id"));
const data = c.req.valid("json");
return c.json({ user: { id, ...data } });
}
)
.delete("/:id", (c) => {
const id = Number(c.req.param("id"));
return c.json({ deleted: id });
});
const app = new Hono().route("/users", users);
// Export the type — this is the key to Hono RPC
export type AppType = typeof app;
export default app;
Client Usage
// src/client.ts
import { hc } from "hono/client";
import type { AppType } from "./server";
// Create the typed client
const client = hc<AppType>("http://localhost:3000");
// All calls are fully type-safe
async function main() {
// GET /users — response is typed
const res = await client.users.$get();
const { users } = await res.json(); // users: { id: number; name: string; email: string }[]
// POST /users — request body is type-checked
const createRes = await client.users.$post({
json: {
name: "Bob",
email: "bob@example.com",
age: 30,
},
});
const { user } = await createRes.json();
// GET /users/:id — typed param
const userRes = await client.users[":id"].$get({ param: { id: "1" } });
const data = await userRes.json();
// PATCH /users/:id
await client.users[":id"].$patch({
param: { id: "1" },
json: { name: "Bob Updated" },
});
// DELETE /users/:id
await client.users[":id"].$delete({ param: { id: "1" } });
}
Query Parameters
// Server — define query schema
const searchSchema = z.object({
q: z.string().optional(),
page: z.coerce.number().default(1),
limit: z.coerce.number().default(20),
});
const app = new Hono().get(
"/search",
zValidator("query", searchSchema),
(c) => {
const { q, page, limit } = c.req.valid("query");
return c.json({ results: [], q, page, limit });
}
);
export type AppType = typeof app;
// Client
const client = hc<AppType>("http://localhost:3000");
const res = await client.search.$get({
query: { q: "hono", page: "1", limit: "10" },
});
Headers and Auth
// Pass headers in the client factory
const client = hc<AppType>("http://localhost:3000", {
headers: {
Authorization: `Bearer ${token}`,
},
});
// Or per-request
const res = await client.users.$get(undefined, {
headers: { "X-Request-ID": crypto.randomUUID() },
});
Cloudflare Workers Deployment
// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono<{ Bindings: CloudflareBindings }>();
// Middleware
app.use("*", logger());
app.use(
"/api/*",
cors({ origin: ["https://myapp.com"], allowMethods: ["GET", "POST", "PUT", "DELETE"] })
);
// Routes with D1 database binding
const api = app
.get("/api/items", async (c) => {
const db = c.env.DB; // Cloudflare D1
const items = await db.prepare("SELECT * FROM items").all();
return c.json({ items: items.results });
})
.post(
"/api/items",
zValidator("json", z.object({ name: z.string(), price: z.number() })),
async (c) => {
const { name, price } = c.req.valid("json");
const db = c.env.DB;
await db.prepare("INSERT INTO items (name, price) VALUES (?, ?)").bind(name, price).run();
return c.json({ ok: true }, 201);
}
);
export type AppType = typeof api;
export default app;
# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "your-database-id"
Sharing Types with Frontend
// packages/api/src/types.ts — shared package
export type { AppType } from "./server";
// packages/web/src/api.ts — frontend
import { hc } from "hono/client";
import type { AppType } from "@myapp/api";
export const api = hc<AppType>(import.meta.env.VITE_API_URL);
// React component — fully typed
const { data } = useQuery({
queryKey: ["users"],
queryFn: async () => {
const res = await api.users.$get();
return res.json();
},
});
Type Inference Helpers
import type { InferRequestType, InferResponseType } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("http://localhost:3000");
// Infer types from client methods
type CreateUserRequest = InferRequestType<typeof client.users.$post>;
type CreateUserResponse = InferResponseType<typeof client.users.$post, 201>;
// Use in form handlers
async function createUser(data: CreateUserRequest["json"]) {
const res = await client.users.$post({ json: data });
const result: CreateUserResponse = await res.json();
return result;
}
Error Handling
// Server — use HTTPException
import { HTTPException } from "hono/http-exception";
app.get("/users/:id", (c) => {
const user = findUser(c.req.param("id"));
if (!user) {
throw new HTTPException(404, { message: "User not found" });
}
return c.json({ user });
});
// Global error handler
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status);
}
console.error(err);
return c.json({ error: "Internal server error" }, 500);
});
// Client — check response status
const res = await client.users[":id"].$get({ param: { id: "999" } });
if (!res.ok) {
const error = await res.json();
console.error("Error:", error);
}
Guidelines
- Export
AppTypefrom the server file astypeof app(not as an interface) to preserve route types. - Keep schemas close to routes so types stay in sync.
- Use
zValidatorfor all user input — body, query, params, headers. - In monorepos, put
AppTypein a shared package so both server and client import from one place. - Use
InferRequestTypeandInferResponseTypeto derive types for form handlers and tests. - Hono RPC works on any runtime — Cloudflare Workers, Deno Deploy, Bun, and Node.js.
- The
hcclient uses nativefetchunder the hood — no special transport needed.