一键导入
hono-idioms
Hono HTTP framework patterns — routing, middleware, Zod validation, RPC client. For TypeScript see typescript-idioms.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Hono HTTP framework patterns — routing, middleware, Zod validation, RPC client. For TypeScript see typescript-idioms.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Structured fault tolerance for coordinator agents. 5-level escalation ladder (Retry → Replace → Skip → Redistribute → Degrade), dead-man timers, degraded completion protocol, and cross-level escalation format. Load when orchestrating agents that may fail.
Structured code review protocol for inspecting code quality against the full rule set. Use when auditing code written by yourself or another agent, during the /audit workflow, or when the user asks for a code review.
Reusable convergence protocol for coordinator agents. Defines the BRIEFING → ITERATE → GATE → CONVERGE loop, context hygiene rules, self-succession protocol, turn budget, and handoff compression. Load when orchestrating multi-iteration workflows.
Pre-flight checklist and post-implementation self-review protocol. Use before generating any code (pre-flight) and after writing code but before verification (self-review) to catch issues early.
MECE task decomposition, file ownership enforcement, DAG-based execution, and safe merge protocol for intra-domain parallel dispatch. The safety invariants that prevent merge chaos when multiple agents write in parallel. Applies recursively at every nesting depth.
Shared protocols for all agents in the multi-agent pipeline: recursive nesting, pre-implementation restatement, parallel dispatch format, and agent definition cascade. Load this skill instead of inlining these protocols in every agent file.
| name | hono-idioms |
| description | Hono HTTP framework patterns — routing, middleware, Zod validation, RPC client. For TypeScript see typescript-idioms. |
| paths | ["**/wrangler.toml"] |
Hono rewards thin handlers, middleware composition, multi-runtime portability, and end-to-end type safety. Idiomatic Hono = typed routes, validator middleware, RPC client — no code generation needed.
Scope: Hono-specific patterns. For TypeScript fundamentals: @.agents/skills/typescript-idioms/SKILL.md. For project structure: @.agents/skills/hono-idioms/references/project-structure.md.
Create apps with new Hono() and method routing:
// ✅ Group by resource, compose with app.route()
const tasks = new Hono()
.get('/', listTasks)
.post('/', createTask)
.get('/:id', getTask)
.put('/:id', updateTask)
.delete('/:id', deleteTask);
const app = new Hono()
.route('/api/tasks', tasks)
.route('/api/users', users);
Use app.route('/prefix', subApp) to compose sub-routers — each feature exports its own Hono instance.
app.basePath('/api/v1') for API versioning — applied once at root level.
app.all('*', handler) for catch-all fallback routes.
Path parameters use :name syntax — accessed via c.req.param('name').
c)Response helpers — always use typed helpers, never raw Response:
// ✅ c.json(), c.text(), c.html(), c.redirect(), c.notFound()
app.get('/tasks/:id', async (c) => {
const task = await taskService.find(c.req.param('id'));
return c.json(task);
});
Request data: c.req.param('id') (path), c.req.query('page') (query string), c.req.header('Authorization') (header), await c.req.json() (body — prefer zValidator instead).
Request-scoped typed variables with c.set() / c.get():
type Env = { Variables: { userId: string; requestId: string } };
const app = new Hono<Env>();
app.use(async (c, next) => {
c.set('requestId', crypto.randomUUID());
await next();
});
app.get('/me', (c) => c.json({ id: c.get('userId') })); // fully typed
Built-in middleware — cors(), logger(), secureHeaders(), compress(), timing(), prettyJSON() (each imported from hono/<name>):
app.use('*', logger(), secureHeaders(), compress());
Global vs scoped middleware:
// ✅ Global — applies to all routes
app.use('*', cors());
// ✅ Scoped — applies only to /api/* routes
app.use('/api/*', authMiddleware);
Custom middleware with createMiddleware<>():
import { createMiddleware } from 'hono/factory';
type AuthEnv = { Variables: { userId: string } };
const authMiddleware = createMiddleware<AuthEnv>(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '');
if (!token) throw new HTTPException(401, { message: 'Unauthorized' });
const payload = await verifyToken(token);
c.set('userId', payload.sub);
await next();
});
Middleware ordering matters — auth before route handlers, logging outermost.
@hono/zod-validator for type-safe request validation:
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200),
priority: z.enum(['low', 'medium', 'high']),
});
// ✅ Compose multiple validators — validated data is fully typed
app.post('/tasks', zValidator('json', CreateTaskSchema), async (c) => {
const body = c.req.valid('json'); // { title: string; priority: 'low'|'medium'|'high' }
const task = await taskService.create(body);
return c.json(task, 201);
});
app.get('/tasks/:id', zValidator('param', z.object({ id: z.string().uuid() })), async (c) => {
const task = await taskService.find(c.req.valid('param').id);
return c.json(task);
});
Validate all input sources: zValidator('json', ...), zValidator('param', ...), zValidator('query', ...), zValidator('header', ...).
app.onError() for global error handling:
import { HTTPException } from 'hono/http-exception';
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);
});
HTTPException for typed HTTP errors:
// ✅ Throw in handlers or middleware — caught by onError
if (!task) throw new HTTPException(404, { message: `Task ${id} not found` });
app.notFound() for custom 404 handling:
app.notFound((c) => c.json({ error: 'Not Found' }, 404));
// server.ts — chain routes and export the type
const routes = app
.get('/tasks', async (c) => c.json(await taskService.list()))
.post('/tasks', zValidator('json', CreateTaskSchema), async (c) => {
return c.json(await taskService.create(c.req.valid('json')), 201);
});
export type AppType = typeof routes;
// client.ts — fully typed, changes propagate compile errors automatically
import { hc } from 'hono/client';
import type { AppType } from './server';
const client = hc<AppType>('http://localhost:3000');
const res = await client.tasks.$post({ json: { title: 'New', priority: 'high' } });
const task = await res.json(); // fully typed
Only the entry point differs — all handler/middleware code is runtime-agnostic:
// Node.js: serve({ fetch: app.fetch, port: 3000 }) // @hono/node-server
// Bun / CF Workers: export default app;
// Deno: Deno.serve(app.fetch);
Never use runtime-specific APIs in handlers — isolate them in platform/ adapters.
For universal testing principles, see
.agents/rules/testing-strategy.md. Below: Hono-specific patterns only.
app.request() — test handlers without starting a server:
import { describe, it, expect } from 'vitest';
import { app } from './app';
describe('GET /api/tasks/:id', () => {
it('returns 200 with task data', async () => {
const res = await app.request('/api/tasks/abc-123');
expect(res.status).toBe(200);
expect((await res.json()).id).toBe('abc-123');
});
it('returns 404 for unknown task', async () => {
const res = await app.request('/api/tasks/unknown');
expect(res.status).toBe(404);
});
});
// POST with body
const res = await app.request('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Test', priority: 'high' }),
});
Mock services via dependency injection — create the Hono app with test doubles in the test setup, not by mocking modules.
streamText for plain-text / LLM token streaming:
import { streamText } from 'hono/streaming';
app.get('/stream/tokens', (c) =>
streamText(c, async (stream) => {
stream.onAbort(() => console.log('client disconnected'));
for (const chunk of ['Hello', ' ', 'World']) {
await stream.write(chunk);
await stream.sleep(100);
}
})
);
streamSSE for Server-Sent Events:
import { streamSSE } from 'hono/streaming';
app.get('/sse', (c) =>
streamSSE(c, async (stream) => {
stream.onAbort(() => cleanup());
let id = 0;
while (true) {
await stream.writeSSE({
data: JSON.stringify({ ts: Date.now() }),
event: 'tick',
id: String(id++),
});
await stream.sleep(1000);
}
})
);
stream for binary or NDJSON (newline-delimited JSON):
import { stream } from 'hono/streaming';
app.get('/stream/tasks', (c) =>
stream(c, async (stream) => {
stream.onAbort(() => cleanup());
const tasks = await taskService.list();
for (const task of tasks) {
await stream.write(JSON.stringify(task) + '\n');
}
})
);
Always call stream.onAbort() to release resources when the client disconnects — without it, the generator keeps running after the connection drops.
JSON.parse(await c.req.text()) — use c.req.json() or zValidator('json', schema) for type-safe parsingHono<{ Variables: ... }> generics for c.set()/c.get()app.use() without path scope — use app.use('/api/*', ...) when middleware should be scoped, not globalSame tooling as TypeScript. See @.agents/skills/typescript-idioms/SKILL.md.