Claude
Skills
Sign in
Back

hono-routing

Included with Lifetime
$97 forever

This skill provides comprehensive knowledge for building type-safe APIs with Hono, focusing on routing patterns, middleware composition, request validation, RPC client/server patterns, error handling, and context management. Use when: building APIs with Hono (any runtime), setting up request validation with Zod/Valibot/Typia/ArkType validators, creating type-safe RPC client/server communication, implementing custom middleware, handling errors with HTTPException, extending Hono context with custom variables, or encountering middleware type inference issues, validation hook confusion, or RPC performance problems. Keywords: hono, hono routing, hono middleware, hono rpc, hono validator, zod validator, valibot validator, type-safe api, hono context, hono error handling, HTTPException, c.req.valid, middleware composition, hono hooks, typed routes, hono client, middleware response not typed, hono validation failed, hono rpc type inference

Backend & APIs

What this skill does


# Hono Routing & Middleware

**Status**: Production Ready ✅
**Last Updated**: 2025-10-22
**Dependencies**: None (framework-agnostic)
**Latest Versions**: [email protected], [email protected], [email protected], @hono/[email protected], @hono/[email protected]

---

## Quick Start (15 Minutes)

### 1. Install Hono

```bash
npm install [email protected]
```

**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 [email protected] @hono/[email protected]
```

```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 6-Part Hono Mastery Guide

### Part 1: Routing Patterns

#### Basic Routes

```typescript
import { Hono } from 'hono'

const app = new Hono()

// GET request
app.get('/posts', (c) => c.json({ posts: [] }))

// POST request
app.post('/posts', (c) => c.json({ created: true }))

// PUT request
app.put('/posts/:id', (c) => c.json({ updated: true }))

// DELETE request
app.delete('/posts/:id', (c) => c.json({ deleted: true }))

// Multiple methods
app.on(['GET', 'POST'], '/multi', (c) => c.text('GET or POST'))

// All methods
app.all('/catch-all', (c) => c.text('Any method'))
```

**Key Points:**
- Always return a Response (c.json, c.text, c.html, etc.)
- Routes are matched in order (first match wins)
- Use specific routes before wildcard routes

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

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

#### Wildcard Routes

```typescript
// Match any path after /api/
app.get('/api/*', (c) => {
  const path = c.req.param('*')
  return c.json({ catchAll: path })
})

// Named wildcard
app.get('/files/:filepath{.+}', (c) => {
  const filepath = c.req.param('filepath')
  return c.json({ file: filepath })
})
```

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

#### Middleware Flow

```typescript
import { Hono } from 'hono'

const app = new Hono()

// Global middleware (runs for all routes)
app.use('*', async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next() // CRITICAL: Must call next()
  console.log('Response sent')
})

// Route-specific middleware
app.use('/admin/*', async (c, next) => {
  // Auth check
  const token = c.req.header('Authorization')
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  await next()
})

app.get('/admin/dashboard', (c) => {
  return c.json({ message: 'Admin Dashboard' })
})
```

**CRITICAL:**
- **Always call `await next()`** in middleware
- Middleware runs BEFORE the handler
- Return early to prevent handler execution
- Check `c.error` AFTER `next()` for error handling

#### 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',
  })
)
```

**Built-in Middleware Reference**: See `references/middleware-catalog.md`

#### Middleware Chaining

```typescript
// Multiple middleware in sequence
app.get(
  '/protected',
  authMiddleware,
  rateLimitMiddleware,
  (c) => {
    return c.json({ data: 'Protected data' })
  }
)

// Middleware factory pattern
const authMiddleware = async (c, next) => {
  const token = c.req.header('Authorization')
  if (!token) {
    throw new HTTPException(401, { message: 'Unauthorized' })
  }

  // Set user in context
  c.set('user', { id: 1, name: 'Alice' })

  await next()
}

const rateLimitMiddleware = async (c, next) => {
  // Rate limit logic
  await next()
}
```

**Why Chain Middleware:**
- Separation of concerns
- Reusable across routes
- Clear execution order

#### Custom Middleware

```typescript
// Timing middleware
const timing = async (c, next) => {
  const start = Date.now()
  await next()
  const elapsed = Date.now() - start
  c.res.headers.set('X-Response-Time', `${elapsed}ms`)
}

// Request ID middleware
const requestId = async (c, next) => {
  const id = crypto.randomUUID()
  c.set('requestId', id)
  await next()
  c.res.headers.set('X-Request-ID', id)
}

// Error logging middleware
const errorLogger = async (c, next) => {
  await next()
  if (c.error) {
    console.error('Error:', c.error)
    // Send to error tracking service
  }
}

app.use('*', timing)
app.use('*', requestId)
app.use('*', errorLogger)
```

**Best Practices:**
- Keep middleware focused (single responsibility)
- Use `c.set()` to share data between middleware
- Check `c.error` AFTER `next()` for error handling
- Return early to short-circuit execution

---

### 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.g
Files: 4
Size: 95.4 KB
Complexity: 49/100
Category: Backend & APIs

Related in Backend & APIs