# Mauriciolauffer Kcalorai Hono Routing

> Hono Routing & Middleware

- Skill: `tomevault-io/mauriciolauffer-kcalorai-hono-routing` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/mauriciolauffer-kcalorai-hono-routing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/mauriciolauffer-kcalorai-hono-routing/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/mauriciolauffer-kcalorai-hono-routing

---


# 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<string, Response>();

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<Env>();

// 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<User>();

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<AppType>("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<AppType>("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<UserRoutes>('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<typeof app>("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

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/mauriciolauffer) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-15 -->

