| name | hono-idioms |
| description | Hono HTTP framework patterns — routing, middleware, Zod validation, RPC client. For TypeScript see typescript-idioms. |
| paths | ["**/wrangler.toml"] |
Hono Idioms and Patterns
Core Philosophy
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.
Loading guard / auto-detection: Hono has no canonical marker file, so it is not reliably auto-detected by file glob (only wrangler.toml — the Cloudflare Workers case — triggers this skill directly). For Node.js / Bun / Deno Hono projects, co-load this skill alongside @.agents/skills/typescript-idioms/SKILL.md whenever hono appears in package.json. Do NOT load this skill for Express, Fastify, or NestJS — it is Hono-only.
When to Load References
Load these before writing code in the matching context — not after.
| Situation | Reference to Load |
|---|
| Starting a Hono project or reviewing file layout | references/project-structure.md |
| TypeScript type system, async, Zod, error types | @.agents/skills/typescript-idioms/SKILL.md (always co-load) |
Zod schemas / boundary validation (zValidator) | @.agents/skills/typescript-idioms/references/zod-patterns.md |
| Async / I/O / coercion / security pitfalls | @.agents/skills/typescript-idioms/references/ts-patterns-and-anti-patterns.md |
| Shared tooling (ESLint, Prettier, Vitest, tsconfig) | @.agents/skills/typescript-idioms/references/recommended-dependencies.md |
Router and Route Organization
-
Create apps with new Hono() and method routing:
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').
Context (c)
-
Response helpers — always use typed helpers, never raw Response:
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() }));
Middleware
-
Built-in middleware — cors(), logger(), secureHeaders(), compress(), timing(), prettyJSON() (each imported from hono/<name>):
app.use('*', logger(), secureHeaders(), compress());
-
Global vs scoped middleware:
app.use('*', cors());
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' });
payload = (token);
c.(, payload.);
();
});
Validation
-
@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']),
});
app.post('/tasks', zValidator('json', CreateTaskSchema), async (c) => {
const body = c.req.valid('json');
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) => {
task = taskService.(c..().);
c.(task);
});
Error Handling
-
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:
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));
RPC Client
- End-to-end type-safe API calls — no code generation:
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;
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.();
Multi-Runtime
-
Only the entry point differs — all handler/middleware code is runtime-agnostic:
-
Never use runtime-specific APIs in handlers — isolate them in platform/ adapters.
Testing
For universal testing principles, see .agents/rules/testing-strategy.md. Below: Hono-specific patterns only.
Test-file naming: *.test.ts co-located next to source (matches the generic-TS convention; see @.agents/skills/typescript-idioms/references/project-structure.md §Test Organization for the cross-framework rule).
-
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);
});
});
const res = await app.request('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
: .({ : , : }),
});
Streaming
-
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.({ : .() }),
: ,
: (id++),
});
stream.();
}
})
);
Anti-Patterns
- ❌ Business logic in handlers — extract to a service/logic layer; handlers only validate, delegate, respond
- ❌ Manual
JSON.parse(await c.req.text()) — use c.req.json() or zValidator('json', schema) for type-safe parsing
- ❌ Untyped context variables — always use
Hono<{ Variables: ... }> generics for c.set()/c.get()
- ❌ Ignoring middleware ordering — auth must run before route handlers; logging outermost
- ❌
app.use() without path scope — use app.use('/api/*', ...) when middleware should be scoped, not global
- ❌ Runtime-specific code in handlers — use adapters at the entry point only; handlers must be portable
Formatting and Static Analysis
Same tooling as TypeScript. See @.agents/skills/typescript-idioms/SKILL.md.
Related
- Code Idioms and Conventions @.agents/rules/code-idioms-and-conventions.md
- TypeScript Idioms @.agents/skills/typescript-idioms/SKILL.md
- API Design Principles @.agents/rules/api-design-principles.md
- Security Principles @.agents/rules/security-principles.md
- Error Handling Principles @.agents/rules/error-handling-principles.md
- Architectural Patterns @.agents/rules/architectural-pattern.md
- Testing Strategy @.agents/rules/testing-strategy.md
- Logging and Observability Mandate @.agents/rules/logging-and-observability-mandate.md
- Logging Implementation @.agents/skills/logging-implementation/SKILL.md