Hono logo

hono

Ultrafast web framework for multiple runtimes

$ npx docs2skills add hono-web-framework
SKILL.md

Hono

Ultrafast web framework for multiple runtimes

What this skill does

Hono is an ultrafast, lightweight web framework built on Web Standards that runs across any JavaScript runtime - Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda, Node.js, and more. It provides Express-like APIs with zero dependencies, exceptional TypeScript support, and built-in middleware for common tasks like authentication, CORS, caching, and validation.

Unlike traditional frameworks tied to specific runtimes, Hono leverages Web Standards (Request/Response/URL APIs) to achieve true portability. Its RegExpRouter achieves industry-leading performance by matching routes with a single large regex, while the tiny preset weighs under 14KB minified. The framework excels at building APIs, proxy servers, edge applications, and full-stack applications with type-safe client-server communication.

Prerequisites

  • JavaScript runtime: Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda, Node.js, etc.
  • TypeScript support recommended
  • No external dependencies required

Quick start

npm create hono@latest
# or
bun create hono@latest

Basic application:

import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

app.get('/json', (c) => {
  return c.json({ message: 'Hello JSON!' })
})

app.post('/posts', async (c) => {
  const body = await c.req.json()
  return c.json({ id: 123, ...body })
})

export default app

Core concepts

Context (c): The primary interface for request/response handling. Provides access to request data, response methods, and middleware state.

Routing: Uses RegExpRouter for ultra-fast pattern matching. Supports path parameters, wildcards, and HTTP method routing.

Middleware: Functions that execute before/after route handlers. Can modify requests, responses, or terminate the chain.

Multi-runtime: Same code runs on any JavaScript runtime that supports Web Standards.

Web Standards: Built on native Request/Response/URL APIs, ensuring portability and performance.

Key API surface

App creation and routing:

const app = new Hono()
app.get(path, handler)
app.post(path, handler) 
app.put(path, handler)
app.delete(path, handler)
app.all(path, handler) // All methods
app.route(path, subApp) // Mount sub-applications

Context methods:

c.req.json() // Parse JSON body
c.req.text() // Parse text body  
c.req.param('key') // Path parameter
c.req.query('key') // Query parameter
c.req.header('key') // Request header
c.json(object) // JSON response
c.text(string) // Text response
c.html(string) // HTML response
c.redirect(url) // Redirect response

Middleware:

app.use(middleware) // Global middleware
app.use('/api/*', middleware) // Path-specific

Common patterns

Basic CRUD API:

const app = new Hono()

app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'User ' + id })
})

app.post('/users', async (c) => {
  const user = await c.req.json()
  return c.json({ id: Date.now(), ...user }, 201)
})

app.put('/users/:id', async (c) => {
  const id = c.req.param('id')
  const updates = await c.req.json()
  return c.json({ id, ...updates })
})

Middleware composition:

import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { jwt } from 'hono/jwt'

const app = new Hono()

// Global middleware
app.use('*', logger())
app.use('*', cors())

// Protected routes
app.use('/api/*', jwt({ secret: 'secret' }))
app.get('/api/protected', (c) => c.json({ user: c.get('jwtPayload') }))

Error handling:

import { HTTPException } from 'hono/http-exception'

app.get('/error', (c) => {
  throw new HTTPException(400, { message: 'Bad Request' })
})

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return err.getResponse()
  }
  return c.text('Internal Server Error', 500)
})

Type-safe routing with validation:

import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const schema = z.object({
  name: z.string(),
  age: z.number()
})

app.post('/users', zValidator('json', schema), (c) => {
  const { name, age } = c.req.valid('json') // Fully typed
  return c.json({ id: 1, name, age })
})

Sub-applications:

const api = new Hono()
api.get('/health', (c) => c.text('OK'))

const app = new Hono()
app.route('/api/v1', api) // Mounts at /api/v1/health

Configuration

Environment variables access:

app.get('/env', (c) => {
  const secret = c.env?.SECRET_KEY // Cloudflare Workers
  return c.text(secret || 'No secret')
})

Custom context type:

type Bindings = {
  DATABASE_URL: string
}

const app = new Hono<{ Bindings: Bindings }>()

Router selection:

import { Hono } from 'hono'
import { RegExpRouter } from 'hono/router/reg-exp-router'

const app = new Hono({ router: new RegExpRouter() })

Best practices

  • Use TypeScript for better DX and type safety
  • Apply middleware in order of execution (logging first, auth last)
  • Use c.env for environment variables in edge runtimes
  • Leverage built-in middleware instead of custom solutions
  • Structure larger apps with sub-applications using app.route()
  • Use validators for type-safe request parsing
  • Handle errors with app.onError() for consistent responses
  • Prefer c.json(), c.text() over manual Response construction
  • Use appropriate router for your use case (RegExpRouter for performance, LinearRouter for cold starts)
  • Keep middleware lightweight for edge performance

Gotchas and common mistakes

Context binding: Don't destructure context methods - const { json } = c breaks this binding. Use c.json() directly.

Async handlers: Always await async context methods like c.req.json() or you'll get Promise objects.

Path parameters: Parameter names must match exactly - /users/:id requires c.req.param('id'), not userId.

Middleware order: Middleware executes in registration order. CORS must come before other middleware that might return early.

Response already sent: Don't call multiple response methods (c.json() then c.text()) - only the first takes effect.

Environment access: c.env is undefined in Node.js - use process.env or pass environment through context variables.

File uploads: Use c.req.parseBody() for multipart forms, not c.req.json().

Cloudflare Workers: The export must be export default app, not named exports.

URL encoding: Query parameters are automatically decoded, path parameters are not - use decodeURIComponent() if needed.

Streaming responses: Use c.stream() for large responses to avoid memory issues in edge environments.

Sub-app mounting: Routes in sub-apps don't inherit parent middleware - apply shared middleware to both.

TypeScript context: Generic context types must be consistent across middleware and handlers in the same chain.