Claude
Skills
Sign in
Back

hono-routing

Included with Lifetime
$97 forever

Build type-safe APIs with Hono - fast, lightweight routing for Cloudflare Workers, Deno, Bun, and Node.js. Set up routing patterns, middleware composition, request validation (Zod/Valibot/Typia/ArkType), RPC client/server with full type inference, and error handling with HTTPException. Use when: building APIs with Hono, setting up request validation with schema libraries, creating type-safe RPC client/server communication, implementing custom middleware chains, handling errors with HTTPException, extending context with custom variables, or troubleshooting middleware type inference issues, validation hook confusion, RPC performance problems, or middleware response typing errors.

Backend & APIsscripts

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: 17
Size: 145.1 KB
Complexity: 87/100
Category: Backend & APIs

Related in Backend & APIs