Skip to content
Back to skills

Hono Idioms

ASecurity

Hono lightweight web framework patterns: type-safe route handlers, middleware composition, Zod validation, and RPC clients for Cloudflare Workers, Node, or Bun. Use when building Hono APIs. Pair with typescript-idioms.

  • 157 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 5, 2026
developmenttypescriptnodeexpresstestingapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 30, 2026

npx -y skills add irahardianto/antigravity-setup --skill hono-idioms --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hono Idioms?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hono Idioms
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/irahardianto-hono-idioms/badge)](https://www.skillsdirectory.com/skills/irahardianto-hono-idioms)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hono-idioms
description: >-
  Hono lightweight web framework patterns: type-safe route handlers, middleware composition, Zod validation, and RPC clients for Cloudflare Workers, Node, or Bun. Use when building Hono APIs. Pair with typescript-idioms.
---

## 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

1. **Create apps with `new Hono()` and method routing:**
   ```typescript
   // ✅ 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);
   ```

2. **Use `app.route('/prefix', subApp)` to compose sub-routers** — each feature exports its own `Hono` instance.

3. **`app.basePath('/api/v1')`** for API versioning — applied once at root level.

4. **`app.all('*', handler)`** for catch-all fallback routes.

5. **Path parameters** use `:name` syntax — accessed via `c.req.param('name')`.

### Context (`c`)

1. **Response helpers — always use typed helpers, never raw `Response`:**
   ```typescript
   // ✅ 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);
   });
   ```

2. **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).

3. **Request-scoped typed variables with `c.set()` / `c.get()`:**
   ```typescript
   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
   ```

### Middleware

1. **Built-in middleware** — `cors()`, `logger()`, `secureHeaders()`, `compress()`, `timing()`, `prettyJSON()` (each imported from `hono/<name>`):
   ```typescript
   app.use('*', logger(), secureHeaders(), compress());
   ```

2. **Global vs scoped middleware:**
   ```typescript
   // ✅ Global — applies to all routes
   app.use('*', cors());

   // ✅ Scoped — applies only to /api/* routes
   app.use('/api/*', authMiddleware);
   ```

3. **Custom middleware with `createMiddleware<>()`:**
   ```typescript
   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();
   });
   ```

4. **Middleware ordering matters** — auth before route handlers, logging outermost.

### Validation

1. **`@hono/zod-validator` for type-safe request validation:**
   ```typescript
   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);
   });
   ```

2. **Validate all input sources:** `zValidator('json', ...)`, `zValidator('param', ...)`, `zValidator('query', ...)`, `zValidator('header', ...)`.

### Error Handling

1. **`app.onError()` for global error handling:**
   ```typescript
   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);
   });
   ```

2. **`HTTPException` for typed HTTP errors:**
   ```typescript
   // ✅ Throw in handlers or middleware — caught by onError
   if (!task) throw new HTTPException(404, { message: `Task ${id} not found` });
   ```

3. **`app.notFound()` for custom 404 handling:**
   ```typescript
   app.notFound((c) => c.json({ error: 'Not Found' }, 404));
   ```

### RPC Client

1. **End-to-end type-safe API calls — no code generation:**
   ```typescript
   // 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
   ```

### Multi-Runtime

1. **Only the entry point differs — all handler/middleware code is runtime-agnostic:**
   ```typescript
   // Node.js:             serve({ fetch: app.fetch, port: 3000 })  // @hono/node-server
   // Bun / CF Workers:    export default app;
   // Deno:                Deno.serve(app.fetch);
   ```

2. **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).

1. **`app.request()` — test handlers without starting a server:**
   ```typescript
   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' }),
   });
   ```

2. **Mock services via dependency injection** — create the `Hono` app with test doubles in the test setup, not by mocking modules.


### Streaming

1. **`streamText` for plain-text / LLM token streaming:**
   ```typescript
   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);
       }
     })
   );
   ```

2. **`streamSSE` for Server-Sent Events:**
   ```typescript
   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);
       }
     })
   );
   ```

3. **`stream` for binary or NDJSON (newline-delimited JSON):**
   ```typescript
   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');
       }
     })
   );
   ```

4. **Always call `stream.onAbort()`** to release resources when the client disconnects — without it, the generator keeps running after the connection drops.

---

### 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

Files in this skill

  • SKILL.md11.5 KB
  • references/project-structure.md3.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.