hono
Ultrafast web framework for multiple runtimes
$ npx docs2skills add hono-web-frameworkHono
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.envfor 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.