--- name: hono-routing description: | Build type-safe APIs with Hono for Cloudflare Workers, Deno, Bun, Node.js. Routing, middleware, validation (Zod/Valibot), RPC, streaming (SSE), WebSocket, security (CSRF, secureHeaders). Use when: building Hono APIs, streaming SSE, WebSocket, validation, RPC. Troubleshoot: validation hooks, RPC types, middleware chains, JWT verify algorithm required (v4.11.4+), body consumed errors. user-invocable: true --- # Hono Routing & Middleware **Status**: Production Ready ✅ **Last Updated**: 2026-01-20 **Dependencies**: None (framework-agnostic) **Latest Versions**: hono@4.11.4, zod@4.3.5, valibot@1.2.0, @hono/zod-validator@0.7.6, @hono/valibot-validator@0.6.1 --- ## Quick Start (15 Minutes) ### 1. Install Hono ```bash npm install hono@4.11.4 ``` **Why Hono:** - **Fast**: Built on Web Standards, runs on any JavaScript runtime - **Lightweight**: ~10KB, no dependencies - **Type-safe**: Full TypeScript support with type inference - **Flexible**: Works on Cloudflare Workers, Deno, Bun, Node.js, Vercel ### 2. Create Basic App ```typescript import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => { return c.json({ message: 'Hello Hono!' }) }) export default app ``` **CRITICAL:** - Use `c.json()`, `c.text()`, `c.html()` for responses - Return the response (don't use `res.send()` like Express) - Export app for runtime (Cloudflare Workers, Deno, Bun, Node.js) ### 3. Add Request Validation ```bash npm install zod@4.3.5 @hono/zod-validator@0.7.6 ``` ```typescript import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const schema = z.object({ name: z.string(), age: z.number(), }) app.post('/user', zValidator('json', schema), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }) }) ``` **Why Validation:** - Type-safe request data - Automatic error responses - Runtime validation, not just TypeScript --- ## The 4-Part Hono Mastery Guide ### Part 1: Routing Patterns #### Route Parameters ```typescript // Single parameter app.get('/users/:id', (c) => { const id = c.req.param('id') return c.json({ userId: id }) }) // Multiple parameters app.get('/posts/:postId/comments/:commentId', (c) => { const { postId, commentId } = c.req.param() return c.json({ postId, commentId }) }) // Optional parameters (using wildcards) app.get('/files/*', (c) => { const path = c.req.param('*') return c.json({ filePath: path }) }) ``` **CRITICAL:** - `c.req.param('name')` returns single parameter - `c.req.param()` returns all parameters as object - Parameters are always strings (cast to number if needed) #### Route Parameter Regex Constraints Use regex patterns in routes to restrict parameter matching at the routing level: ```typescript // Only matches numeric IDs app.get('/users/:id{[0-9]+}', (c) => { const id = c.req.param('id') // Guaranteed to be digits return c.json({ userId: id }) }) // Only matches UUIDs app.get('/posts/:id{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}', (c) => { const id = c.req.param('id') // Guaranteed to be UUID format return c.json({ postId: id }) }) ``` **Benefits:** - Early validation at routing level - Prevents invalid requests from reaching handlers - Self-documenting route constraints #### Query Parameters ```typescript app.get('/search', (c) => { // Single query param const q = c.req.query('q') // Multiple query params const { page, limit } = c.req.query() // Query param array (e.g., ?tag=js&tag=ts) const tags = c.req.queries('tag') return c.json({ q, page, limit, tags }) }) ``` **Best Practice:** - Use validation for query params (see Part 4) - Provide defaults for optional params - Parse numbers/booleans from query strings #### Route Grouping (Sub-apps) ```typescript // Create sub-app const api = new Hono() api.get('/users', (c) => c.json({ users: [] })) api.get('/posts', (c) => c.json({ posts: [] })) // Mount sub-app const app = new Hono() app.route('/api', api) // Result: /api/users, /api/posts ``` **Why Group Routes:** - Organize large applications - Share middleware for specific routes - Better code structure and maintainability --- ### Part 2: Middleware & Validation **CRITICAL Middleware Rule:** - **Always call `await next()`** in middleware to continue the chain - Return early (without calling `next()`) to prevent handler execution - Check `c.error` AFTER `next()` for error handling ```typescript app.use('/admin/*', async (c, next) => { const token = c.req.header('Authorization') if (!token) return c.json({ error: 'Unauthorized' }, 401) await next() // Required! }) ``` #### Built-in Middleware ```typescript import { Hono } from 'hono' import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { prettyJSON } from 'hono/pretty-json' import { compress } from 'hono/compress' import { cache } from 'hono/cache' const app = new Hono() // Request logging app.use('*', logger()) // CORS app.use('/api/*', cors({ origin: 'https://example.com', allowMethods: ['GET', 'POST', 'PUT', 'DELETE'], allowHeaders: ['Content-Type', 'Authorization'], })) // Pretty JSON (dev only) app.use('*', prettyJSON()) // Compression (gzip/deflate) app.use('*', compress()) // Cache responses app.use( '/static/*', cache({ cacheName: 'my-app', cacheControl: 'max-age=3600', }) ) ``` **Custom Cache Middleware Pattern:** When implementing custom cache middleware for Node.js (or other non-Cloudflare runtimes), you must clone responses before storing them in cache: ```typescript const cache = new Map() const customCache = async (c, next) => { const key = c.req.url // Check cache const cached = cache.get(key) if (cached) { return cached.clone() // Clone when returning from cache } // Execute handler await next() // Store in cache (must clone!) cache.set(key, c.res.clone()) // ✅ Clone before storing } app.use('*', customCache) ``` **Why Cloning is Required:** Response bodies are readable streams that can only be consumed once. Cloning creates a new response with a fresh stream. ``` **Built-in Middleware Reference**: See `references/middleware-catalog.md` #### Streaming Helpers (SSE, AI Responses) ```typescript import { Hono } from 'hono' import { stream, streamText, streamSSE } from 'hono/streaming' const app = new Hono() // Binary streaming app.get('/download', (c) => { return stream(c, async (stream) => { await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])) await stream.pipe(readableStream) }) }) // Text streaming (AI responses) app.get('/ai', (c) => { return streamText(c, async (stream) => { for await (const chunk of aiResponse) { await stream.write(chunk) await stream.sleep(50) // Rate limit if needed } }) }) // Server-Sent Events (real-time updates) app.get('/sse', (c) => { return streamSSE(c, async (stream) => { let id = 0 while (true) { await stream.writeSSE({ data: JSON.stringify({ time: Date.now() }), event: 'update', id: String(id++), }) await stream.sleep(1000) } }) }) ``` **Use Cases:** - `stream()` - Binary files, video, audio - `streamText()` - AI chat responses, typewriter effects - `streamSSE()` - Real-time notifications, live feeds #### WebSocket Helper ```typescript import { Hono } from 'hono' import { upgradeWebSocket } from 'hono/cloudflare-workers' // Platform-specific! const app = new Hono() app.get('/ws', upgradeWebSocket((c) => ({ onMessage(event, ws) { console.log(`Message: ${event.data}`) ws.send(`Echo: ${event.data}`) }, onClose: () => console.log('Closed'), onError: (event) => console.error('Error:', event), // onOpen is NOT supported on Cloudflare Workers! }))) export default app ``` **⚠️ Cloudflare Workers WebSocket Caveats:** - Import from `hono/cloudflare-workers` (not `hono/ws`) - `onOpen` callback is **NOT supported** (Cloudflare limitation) - CORS/header-modifying middleware conflicts with WebSocket routes - Use route grouping to exclude WebSocket routes from CORS: ```typescript const api = new Hono() api.use('*', cors()) // CORS for API only app.route('/api', api) app.get('/ws', upgradeWebSocket(...)) // No CORS on WebSocket ``` #### Security Middleware ```typescript import { Hono } from 'hono' import { secureHeaders } from 'hono/secure-headers' import { csrf } from 'hono/csrf' const app = new Hono() // Security headers (X-Frame-Options, CSP, HSTS, etc.) app.use('*', secureHeaders({ xFrameOptions: 'DENY', xXssProtection: '1; mode=block', contentSecurityPolicy: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], }, })) // CSRF protection (validates Origin header) app.use('/api/*', csrf({ origin: ['https://example.com', 'https://admin.example.com'], })) ``` **Security Middleware Options:** | Middleware | Purpose | |------------|---------| | `secureHeaders` | X-Frame-Options, CSP, HSTS, XSS protection | | `csrf` | CSRF via Origin/Sec-Fetch-Site validation | | `bearerAuth` | Bearer token authentication | | `basicAuth` | HTTP Basic authentication | | `ipRestriction` | IP allowlist/blocklist | #### Combine Middleware Compose middleware with conditional logic: ```typescript import { Hono } from 'hono' import { some, every, except } from 'hono/combine' import { bearerAuth } from 'hono/bearer-auth' import { ipRestriction } from 'hono/ip-restriction' const app = new Hono() // some: ANY middleware must pass (OR logic) app.use('/admin/*', some( bearerAuth({ token: 'admin-token' }), ipRestriction({ allowList: ['10.0.0.0/8'] }), )) // every: ALL middleware must pass (AND logic) app.use('/secure/*', every( bearerAuth({ token: 'secret' }), ipRestriction({ allowList: ['192.168.1.0/24'] }), )) // except: Skip middleware for certain paths app.use('*', except( ['/health', '/metrics'], logger(), )) ``` --- ### Part 3: Type-Safe Context Extension #### Using c.set() and c.get() ```typescript import { Hono } from 'hono' type Bindings = { DATABASE_URL: string } type Variables = { user: { id: number name: string } requestId: string } const app = new Hono<{ Bindings: Bindings; Variables: Variables }>() // Middleware sets variables app.use('*', async (c, next) => { c.set('requestId', crypto.randomUUID()) await next() }) app.use('/api/*', async (c, next) => { c.set('user', { id: 1, name: 'Alice' }) await next() }) // Route accesses variables app.get('/api/profile', (c) => { const user = c.get('user') // Type-safe! const requestId = c.get('requestId') // Type-safe! return c.json({ user, requestId }) }) ``` **CRITICAL:** - Define `Variables` type for type-safe `c.get()` - Define `Bindings` type for environment variables (Cloudflare Workers) - `c.set()` in middleware, `c.get()` in handlers #### Custom Context Extension ```typescript import { Hono } from 'hono' import type { Context } from 'hono' type Env = { Variables: { logger: { info: (message: string) => void error: (message: string) => void } } } const app = new Hono() // Create logger middleware app.use('*', async (c, next) => { const logger = { info: (msg: string) => console.log(`[INFO] ${msg}`), error: (msg: string) => console.error(`[ERROR] ${msg}`), } c.set('logger', logger) await next() }) app.get('/', (c) => { const logger = c.get('logger') logger.info('Hello from route') return c.json({ message: 'Hello' }) }) ``` **Advanced Pattern**: See `templates/context-extension.ts` --- ### Part 4: Request Validation #### Validation with Zod ```bash npm install zod@4.3.5 @hono/zod-validator@0.7.6 ``` ```typescript import { zValidator } from '@hono/zod-validator' import { z } from 'zod' // Define schema const userSchema = z.object({ name: z.string().min(1).max(100), email: z.string().email(), age: z.number().int().min(18).optional(), }) // Validate JSON body app.post('/users', zValidator('json', userSchema), (c) => { const data = c.req.valid('json') // Type-safe! return c.json({ success: true, data }) }) // Validate query params const searchSchema = z.object({ q: z.string(), page: z.string().transform((val) => parseInt(val, 10)), limit: z.string().transform((val) => parseInt(val, 10)).optional(), }) app.get('/search', zValidator('query', searchSchema), (c) => { const { q, page, limit } = c.req.valid('query') return c.json({ q, page, limit }) }) // Validate route params const idSchema = z.object({ id: z.string().uuid(), }) app.get('/users/:id', zValidator('param', idSchema), (c) => { const { id } = c.req.valid('param') return c.json({ userId: id }) }) // Validate headers const headerSchema = z.object({ 'authorization': z.string().startsWith('Bearer '), 'content-type': z.string(), }) app.post('/auth', zValidator('header', headerSchema), (c) => { const headers = c.req.valid('header') return c.json({ authenticated: true }) }) ``` **CRITICAL:** - **Always use `c.req.valid()`** after validation (type-safe) - Validation targets: `json`, `query`, `param`, `header`, `form`, `cookie` - Use `z.transform()` to convert strings to numbers/dates - Validation errors return 400 automatically **⚠️ CRITICAL: Validation Must Be Handler-Specific** For validated types to be inferred correctly, validation middleware **must be added in the handler**, not via `app.use()`: ```typescript // ❌ WRONG - Type inference breaks app.use('/users', zValidator('json', userSchema)) app.post('/users', (c) => { const data = c.req.valid('json') // TS Error: Type 'never' return c.json({ data }) }) // ✅ CORRECT - Validation in handler app.post('/users', zValidator('json', userSchema), (c) => { const data = c.req.valid('json') // Type-safe! return c.json({ data }) }) ``` **Why It Happens:** Hono's `Input` type mapping merges validation results using generics. When validators are applied via `app.use()`, the type system cannot track which routes have which validation schemas, causing the `Input` generic to collapse to `never`. #### Custom Validation Hooks ```typescript import { zValidator } from '@hono/zod-validator' import { HTTPException } from 'hono/http-exception' const schema = z.object({ name: z.string(), age: z.number(), }) // Custom error handler app.post( '/users', zValidator('json', schema, (result, c) => { if (!result.success) { // Custom error response return c.json( { error: 'Validation failed', issues: result.error.issues, }, 400 ) } }), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }) } ) // Throw HTTPException app.post( '/users', zValidator('json', schema, (result, c) => { if (!result.success) { throw new HTTPException(400, { cause: result.error }) } }), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }) } ) ``` **Note on Zod Optional Enums:** Prior to `@hono/zod-validator@0.7.6`, optional enums incorrectly resolved to strings instead of the enum type. This was fixed in v0.7.6. Ensure you're using the latest version: ```bash npm install @hono/zod-validator@0.7.6 ``` #### Validation with Valibot ```bash npm install valibot@1.2.0 @hono/valibot-validator@0.6.1 ``` ```typescript import { vValidator } from '@hono/valibot-validator' import * as v from 'valibot' const schema = v.object({ name: v.string(), age: v.number(), }) app.post('/users', vValidator('json', schema), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }) }) ``` **Zod vs Valibot**: See `references/validation-libraries.md` #### Validation with Typia ```bash npm install typia @hono/typia-validator@0.1.2 ``` ```typescript import { typiaValidator } from '@hono/typia-validator' import typia from 'typia' interface User { name: string age: number } const validate = typia.createValidate() app.post('/users', typiaValidator('json', validate), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }) }) ``` **Why Typia:** - Fastest validation (compile-time) - No runtime schema definition - AOT (Ahead-of-Time) compilation #### Validation with ArkType ```bash npm install arktype @hono/arktype-validator@2.0.1 ``` ```typescript import { arktypeValidator } from '@hono/arktype-validator' import { type } from 'arktype' const schema = type({ name: 'string', age: 'number', }) app.post('/users', arktypeValidator('json', schema), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }) }) ``` **Comparison**: See `references/validation-libraries.md` for detailed comparison --- ### Part 5: Typed Routes (RPC) #### Why RPC? Hono's RPC feature allows **type-safe client/server communication** without manual API type definitions. The client infers types directly from the server routes. #### Server-Side Setup ```typescript // app.ts import { Hono } from 'hono' import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const app = new Hono() const schema = z.object({ name: z.string(), age: z.number(), }) // Define route and export type const route = app.post( '/users', zValidator('json', schema), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }, 201) } ) // Export app type for RPC client export type AppType = typeof route // OR export entire app // export type AppType = typeof app export default app ``` **CRITICAL:** - **Must use `const route = app.get(...)` for RPC type inference** - Export `typeof route` or `typeof app` - Don't use anonymous route definitions #### Client-Side Setup ```typescript // client.ts import { hc } from 'hono/client' import type { AppType } from './app' const client = hc('http://localhost:8787') // Type-safe API call const res = await client.users.$post({ json: { name: 'Alice', age: 30, }, }) // Response is typed! const data = await res.json() // { success: boolean, data: { name: string, age: number } } ``` **Why RPC:** - ✅ Full type inference (request + response) - ✅ No manual type definitions - ✅ Compile-time error checking - ✅ Auto-complete in IDE **⚠️ RPC Type Inference Limitation:** The RPC client only infers types for `json` and `text` responses. If an endpoint returns multiple response types (e.g., JSON and binary), **none** of the responses will be type-inferred: ```typescript // ❌ Type inference fails - mixes JSON and binary app.post('/upload', async (c) => { const body = await c.req.body() // Binary response if (error) { return c.json({ error: 'Bad request' }, 400) // JSON response } return c.json({ success: true }) }) // ✅ Separate endpoints by response type app.post('/upload', async (c) => { return c.json({ success: true }) // Only JSON - types work }) app.get('/download/:id', async (c) => { return c.body(binaryData) // Only binary - separate endpoint }) ``` #### RPC with Multiple Routes ```typescript // Server const app = new Hono() const getUsers = app.get('/users', (c) => { return c.json({ users: [] }) }) const createUser = app.post( '/users', zValidator('json', userSchema), (c) => { const data = c.req.valid('json') return c.json({ success: true, data }, 201) } ) const getUser = app.get('/users/:id', (c) => { const id = c.req.param('id') return c.json({ id, name: 'Alice' }) }) // Export combined type export type AppType = typeof getUsers | typeof createUser | typeof getUser // Client const client = hc('http://localhost:8787') // GET /users const usersRes = await client.users.$get() // POST /users const createRes = await client.users.$post({ json: { name: 'Alice', age: 30 }, }) // GET /users/:id const userRes = await client.users[':id'].$get({ param: { id: '123' }, }) ``` #### RPC Performance Optimization **Problem**: Large apps with many routes cause slow type inference **Solution**: Export specific route groups instead of entire app ```typescript // ❌ Slow: Export entire app export type AppType = typeof app // ✅ Fast: Export specific routes const userRoutes = app.get('/users', ...).post('/users', ...) export type UserRoutes = typeof userRoutes const postRoutes = app.get('/posts', ...).post('/posts', ...) export type PostRoutes = typeof postRoutes // Client imports specific routes import type { UserRoutes } from './app' const userClient = hc('http://localhost:8787') ``` **Deep Dive**: See `references/rpc-guide.md` --- ### Part 6: Error Handling #### HTTPException ```typescript import { Hono } from 'hono' import { HTTPException } from 'hono/http-exception' const app = new Hono() app.get('/users/:id', (c) => { const id = c.req.param('id') // Throw HTTPException for client errors if (!id) { throw new HTTPException(400, { message: 'ID is required' }) } // With custom response if (id === 'invalid') { const res = new Response('Custom error body', { status: 400 }) throw new HTTPException(400, { res }) } return c.json({ id }) }) ``` **CRITICAL:** - Use HTTPException for **expected errors** (400, 401, 403, 404) - Don't use for **unexpected errors** (500) - use `onError` instead - HTTPException stops execution immediately #### Global Error Handler (onError) ```typescript import { Hono } from 'hono' import { HTTPException } from 'hono/http-exception' const app = new Hono() // Custom error handler app.onError((err, c) => { // Handle HTTPException if (err instanceof HTTPException) { return err.getResponse() } // Handle unexpected errors console.error('Unexpected error:', err) return c.json( { error: 'Internal Server Error', message: err.message, }, 500 ) }) app.get('/error', (c) => { throw new Error('Something went wrong!') }) ``` **Why onError:** - Centralized error handling - Consistent error responses - Error logging and tracking #### Middleware Error Checking ```typescript app.use('*', async (c, next) => { await next() // Check for errors after handler if (c.error) { console.error('Error in route:', c.error) // Send to error tracking service } }) ``` #### Not Found Handler ```typescript app.notFound((c) => { return c.json({ error: 'Not Found' }, 404) }) ``` --- ## Critical Rules ### Always Do ✅ **Call `await next()` in middleware** - Required for middleware chain execution ✅ **Return Response from handlers** - Use `c.json()`, `c.text()`, `c.html()` ✅ **Use `c.req.valid()` after validation** - Type-safe validated data ✅ **Export route types for RPC** - `export type AppType = typeof route` ✅ **Throw HTTPException for client errors** - 400, 401, 403, 404 errors ✅ **Use `onError` for global error handling** - Centralized error responses ✅ **Define Variables type for c.set/c.get** - Type-safe context variables ✅ **Use const route = app.get(...)** - Required for RPC type inference ### Never Do ❌ **Forget `await next()` in middleware** - Breaks middleware chain ❌ **Use `res.send()` like Express** - Not compatible with Hono ❌ **Access request data without validation** - Use validators for type safety ❌ **Export entire app for large RPC** - Slow type inference, export specific routes ❌ **Use plain throw new Error()** - Use HTTPException instead ❌ **Skip onError handler** - Leads to inconsistent error responses ❌ **Use c.set/c.get without Variables type** - Loses type safety --- ## Known Issues Prevention This skill prevents **10** documented issues: ### Issue #1: RPC Type Inference Slow **Error**: IDE becomes slow with many routes (8-minute CI builds, non-existent IntelliSense) **Source**: [hono/docs/guides/rpc](https://hono.dev/docs/guides/rpc) | [GitHub Issue #3869](https://github.com/honojs/hono/issues/3869) **Why It Happens**: Complex type instantiation from `typeof app` with many routes. Exacerbated by Zod methods like `omit`, `extend`, `pick`. **Prevention**: Export specific route groups instead of entire app ```typescript // ❌ Slow export type AppType = typeof app // ✅ Fast const userRoutes = app.get(...).post(...) export type UserRoutes = typeof userRoutes ``` **Advanced Workaround for Large Apps** (100+ routes): 1. **Split into monorepo libs**: ```typescript // routers-auth/index.ts export const authRouter = new Hono() .get('/login', ...) .post('/login', ...) // routers-orders/index.ts export const orderRouter = new Hono() .get('/orders', ...) .post('/orders', ...) // routers-main/index.ts const app = new Hono() .route('/auth', authRouter) .route('/orders', orderRouter) export type AppType = typeof app ``` 2. **Use separate build configs**: - **Production**: Full `tsc` with `.d.ts` generation (for RPC client) - **Development**: Skip `tsc` on main router, only type-check sub-routers (faster live-reload) 3. **Avoid Zod methods that hurt performance**: - `z.omit()`, `z.extend()`, `z.pick()` - These increase language server workload by 10x - Use interfaces instead of intersections when possible ### Issue #2: Middleware Response Not Typed in RPC **Error**: Middleware responses (including `notFound()` and `onError()`) not inferred by RPC client **Source**: [honojs/hono#2719](https://github.com/honojs/hono/issues/2719) | [GitHub Issue #4600](https://github.com/honojs/hono/issues/4600) **Why It Happens**: RPC mode doesn't infer middleware responses by default. Responses from `notFound()` or `onError()` handlers are not included in type map. **Prevention**: Export specific route types that include middleware ```typescript const route = app.get( '/data', myMiddleware, (c) => c.json({ data: 'value' }) ) export type AppType = typeof route ``` **Specific Issue: notFound/onError Not Typed:** ```typescript // Server const app = new Hono() .notFound((c) => c.json({ error: 'Not Found' }, 404)) .get('/users/:id', async (c) => { const user = await getUser(c.req.param('id')) if (!user) { return c.notFound() // Type not exported to RPC client } return c.json({ user }) }) // Client const client = hc('http://localhost:8787') const res = await client.users[':id'].$get({ param: { id: '123' } }) if (res.status === 404) { const error = await res.json() // Type is 'any', not { error: string } } ``` **Partial Workaround** (v4.11.0+): Use module augmentation to customize `NotFoundResponse` type: ```typescript import { Hono, TypedResponse } from 'hono' declare module 'hono' { interface NotFoundResponse extends Response, TypedResponse<{ error: string }, 404, 'json'> {} } ``` ### Issue #3: Validation Hook Confusion **Error**: Different validator libraries have different hook patterns **Source**: Context7 research **Why It Happens**: Each validator (@hono/zod-validator, @hono/valibot-validator, etc.) has slightly different APIs **Prevention**: This skill provides consistent patterns for all validators ### Issue #4: HTTPException Misuse **Error**: Throwing plain Error instead of HTTPException **Source**: Official docs **Why It Happens**: Developers familiar with Express use `throw new Error()` **Prevention**: Always use `HTTPException` for client errors (400-499) ```typescript // ❌ Wrong throw new Error('Unauthorized') // ✅ Correct throw new HTTPException(401, { message: 'Unauthorized' }) ``` ### Issue #5: Context Type Safety Lost **Error**: `c.set()` and `c.get()` without type inference **Source**: Official docs **Why It Happens**: Not defining `Variables` type in Hono generic **Prevention**: Always define Variables type ```typescript type Variables = { user: { id: number; name: string } } const app = new Hono<{ Variables: Variables }>() ``` ### Issue #6: Missing Error Check After Middleware **Error**: Errors in handlers not caught **Source**: Official docs **Why It Happens**: Not checking `c.error` after `await next()` **Prevention**: Check `c.error` in middleware ```typescript app.use('*', async (c, next) => { await next() if (c.error) { console.error('Error:', c.error) } }) ``` ### Issue #7: Direct Request Access Without Validation **Error**: Accessing `c.req.param()` or `c.req.query()` without validation **Source**: Best practices **Why It Happens**: Developers skip validation for speed **Prevention**: Always use validators and `c.req.valid()` ```typescript // ❌ Wrong const id = c.req.param('id') // string, no validation // ✅ Correct app.get('/users/:id', zValidator('param', idSchema), (c) => { const { id } = c.req.valid('param') // validated UUID }) ``` ### Issue #8: Incorrect Middleware Order **Error**: Middleware executing in wrong order **Source**: Official docs **Why It Happens**: Misunderstanding middleware chain execution **Prevention**: Remember middleware runs top-to-bottom, `await next()` runs handler, then bottom-to-top ```typescript app.use('*', async (c, next) => { console.log('1: Before handler') await next() console.log('4: After handler') }) app.use('*', async (c, next) => { console.log('2: Before handler') await next() console.log('3: After handler') }) app.get('/', (c) => { console.log('Handler') return c.json({}) }) // Output: 1, 2, Handler, 3, 4 ``` ### Issue #9: JWT verify() Requires Algorithm Parameter (v4.11.4+) **Error**: `TypeError: Cannot read properties of undefined` **Source**: [GitHub Issue #4625](https://github.com/honojs/hono/issues/4625) | [Security Advisory GHSA-f67f-6cw9-8mq4](https://github.com/honojs/hono/security/advisories/GHSA-f67f-6cw9-8mq4) **Why It Happens**: Security fix in v4.11.4 requires explicit algorithm specification to prevent JWT header manipulation **Prevention**: Always specify the algorithm parameter ```typescript import { verify } from 'hono/jwt' // ❌ Wrong (pre-v4.11.4 syntax) const payload = await verify(token, secret) // ✅ Correct (v4.11.4+) const payload = await verify(token, secret, 'HS256') // Algorithm required ``` **Note**: This was a breaking change released in a patch version due to security severity. Update all JWT verification code when upgrading to v4.11.4+. ### Issue #10: Request Body Consumed by Middleware **Error**: `TypeError: Body is unusable` **Source**: [GitHub Issue #4259](https://github.com/honojs/hono/issues/4259) **Why It Happens**: Using `c.req.raw.clone()` bypasses Hono's cache and consumes the body stream **Prevention**: Always use `c.req.text()` or `c.req.json()` instead of accessing raw request ```typescript // ❌ Wrong - Breaks downstream validators app.use('*', async (c, next) => { const body = await c.req.raw.clone().text() // Consumes body! console.log('Request body:', body) await next() }) app.post('/', zValidator('json', schema), async (c) => { const data = c.req.valid('json') // Error: Body is unusable return c.json({ data }) }) // ✅ Correct - Uses cached content app.use('*', async (c, next) => { const body = await c.req.text() // Cache-friendly console.log('Request body:', body) await next() }) app.post('/', zValidator('json', schema), async (c) => { const data = c.req.valid('json') // Works! return c.json({ data }) }) ``` **Why**: Request bodies in Web APIs can only be read once (they're streams). Hono's validator internally uses `await c.req.json()` which caches the content. If you use `c.req.raw.clone().json()`, it bypasses the cache and consumes the body, causing subsequent reads to fail. --- ## Configuration Files Reference ### package.json (Full Example) ```json { "name": "hono-app", "version": "1.0.0", "type": "module", "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js" }, "dependencies": { "hono": "^4.11.4" }, "devDependencies": { "typescript": "^5.9.0", "tsx": "^4.19.0", "@types/node": "^22.10.0" } } ``` ### package.json with Validation (Zod) ```json { "dependencies": { "hono": "^4.11.4", "zod": "^4.3.5", "@hono/zod-validator": "^0.7.6" } } ``` ### package.json with Validation (Valibot) ```json { "dependencies": { "hono": "^4.11.4", "valibot": "^1.2.0", "@hono/valibot-validator": "^0.6.1" } } ``` ### package.json with All Validators ```json { "dependencies": { "hono": "^4.11.4", "zod": "^4.3.5", "valibot": "^1.2.0", "@hono/zod-validator": "^0.7.6", "@hono/valibot-validator": "^0.6.1", "@hono/typia-validator": "^0.1.2", "@hono/arktype-validator": "^2.0.1" } } ``` ### tsconfig.json ```json { "compilerOptions": { "target": "ES2022", "module": "ES2022", "lib": ["ES2022"], "moduleResolution": "bundler", "resolveJsonModule": true, "allowJs": true, "checkJs": false, "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "outDir": "./dist" }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` --- ## File Templates All templates are available in the `templates/` directory: - **routing-patterns.ts** - Route params, query params, wildcards, grouping - **middleware-composition.ts** - Middleware chaining, built-in middleware - **validation-zod.ts** - Zod validation with custom hooks - **validation-valibot.ts** - Valibot validation - **rpc-pattern.ts** - Type-safe RPC client/server - **error-handling.ts** - HTTPException, onError, custom errors - **context-extension.ts** - c.set/c.get, custom context types - **package.json** - All dependencies Copy these files to your project and customize as needed. --- ## Reference Documentation For deeper understanding, see: - **middleware-catalog.md** - Complete built-in Hono middleware reference - **validation-libraries.md** - Zod vs Valibot vs Typia vs ArkType comparison - **rpc-guide.md** - RPC pattern deep dive, performance optimization - **top-errors.md** - Common Hono errors with solutions --- ## Official Documentation - **Hono**: https://hono.dev - **Hono Routing**: https://hono.dev/docs/api/routing - **Hono Middleware**: https://hono.dev/docs/guides/middleware - **Hono Validation**: https://hono.dev/docs/guides/validation - **Hono RPC**: https://hono.dev/docs/guides/rpc - **Hono Context**: https://hono.dev/docs/api/context - **Context7 Library ID**: `/llmstxt/hono_dev_llms-full_txt` --- ## Dependencies (Latest Verified 2026-01-20) ```json { "dependencies": { "hono": "^4.11.4" }, "optionalDependencies": { "zod": "^4.3.5", "valibot": "^1.2.0", "@hono/zod-validator": "^0.7.6", "@hono/valibot-validator": "^0.6.1", "@hono/typia-validator": "^0.1.2", "@hono/arktype-validator": "^2.0.1" }, "devDependencies": { "typescript": "^5.9.0" } } ``` --- ## Production Example This skill is validated across multiple runtime environments: - **Cloudflare Workers**: Routing, middleware, RPC patterns - **Deno**: All validation libraries tested - **Bun**: Performance benchmarks completed - **Node.js**: Full test suite passing All patterns in this skill have been validated in production. --- **Questions? Issues?** 1. Check `references/top-errors.md` first 2. Verify all steps in the setup process 3. Ensure `await next()` is called in middleware 4. Ensure RPC routes use `const route = app.get(...)` pattern 5. Check official docs: https://hono.dev