# Hono - Ultrafast Web Framework
Hono (meaning "flame" in Japanese) is a small, simple, and ultrafast web framework built on Web Standards. It works on any JavaScript runtime including Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge, and Node.js. The framework provides first-class TypeScript support with type inference for route parameters, request validation, and RPC clients.
Hono is designed to be lightweight (under 14KB with the `hono/tiny` preset) with zero dependencies, using only Web Standards APIs. It features multiple router implementations (RegExpRouter for speed, LinearRouter for quick registration, SmartRouter for flexibility), built-in middleware for common tasks like authentication, CORS, and logging, plus a JSX renderer for server-side HTML generation. The RPC feature enables end-to-end type safety between server and client.
## Basic Application Setup
Create a new Hono application instance and define route handlers using HTTP method functions. Export the app for your runtime environment.
```typescript
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
app.post('/posts', (c) => c.json({ message: 'Created!' }, 201))
app.put('/posts/:id', (c) => c.text(`Updated ${c.req.param('id')}`))
app.delete('/posts/:id', (c) => c.text(`Deleted ${c.req.param('id')}`))
// Handle any HTTP method
app.all('/hello', (c) => c.text('Any Method /hello'))
// Custom HTTP method
app.on('PURGE', '/cache', (c) => c.text('Cache purged'))
// Multiple methods on same path
app.on(['PUT', 'DELETE'], '/resource', (c) => c.text('PUT or DELETE'))
export default app
```
## Path Parameters and Query Strings
Access URL path parameters with `c.req.param()` and query string values with `c.req.query()`. Hono provides full TypeScript inference for parameter names.
```typescript
import { Hono } from 'hono'
const app = new Hono()
// Single path parameter
app.get('/user/:name', (c) => {
const name = c.req.param('name') // TypeScript knows this is string
return c.text(`Hello, ${name}!`)
})
// Multiple path parameters
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param()
return c.json({ postId, commentId })
})
// Optional parameter
app.get('/api/animal/:type?', (c) => {
const type = c.req.param('type') ?? 'unknown'
return c.text(`Animal type: ${type}`)
})
// Regex pattern in parameter
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
return c.json({ date, title })
})
// Query parameters
app.get('/search', (c) => {
const query = c.req.query('q')
const { limit, offset } = c.req.query()
return c.json({ query, limit, offset })
})
// Multiple values for same query key (?tags=A&tags=B)
app.get('/filter', (c) => {
const tags = c.req.queries('tags') // string[]
return c.json({ tags })
})
```
## Context Response Methods
The Context object provides methods for returning different response types with proper Content-Type headers.
```typescript
import { Hono } from 'hono'
const app = new Hono()
// Plain text response
app.get('/text', (c) => c.text('Hello World'))
// JSON response
app.get('/json', (c) => {
return c.json({
message: 'Hello',
timestamp: Date.now()
})
})
// HTML response
app.get('/html', (c) => {
return c.html('
Hello Hono!
')
})
// Set status code
app.post('/posts', (c) => {
c.status(201)
return c.json({ message: 'Created!' })
})
// Set custom headers
app.get('/custom', (c) => {
c.header('X-Custom-Header', 'CustomValue')
c.header('Cache-Control', 'no-cache')
return c.text('With custom headers')
})
// Redirect
app.get('/old-page', (c) => c.redirect('/new-page'))
app.get('/moved', (c) => c.redirect('/new-location', 301))
// Body with status and headers in one call
app.get('/full', (c) => {
return c.body('Response body', 200, {
'X-Message': 'Hello',
'Content-Type': 'text/plain'
})
})
```
## Request Body Parsing
Parse different request body formats using the HonoRequest methods.
```typescript
import { Hono } from 'hono'
const app = new Hono()
// JSON body
app.post('/api/data', async (c) => {
const body = await c.req.json()
return c.json({ received: body })
})
// Form data (multipart/form-data or application/x-www-form-urlencoded)
app.post('/form', async (c) => {
const body = await c.req.parseBody()
const name = body['name'] // string | File
return c.text(`Name: ${name}`)
})
// Multiple files with same field name
app.post('/upload-multiple', async (c) => {
const body = await c.req.parseBody()
const files = body['files[]'] // (string | File)[]
return c.json({ fileCount: Array.isArray(files) ? files.length : 1 })
})
// All form fields including multiple values
app.post('/form-all', async (c) => {
const body = await c.req.parseBody({ all: true })
return c.json(body)
})
// Plain text body
app.post('/text', async (c) => {
const text = await c.req.text()
return c.text(`Received: ${text}`)
})
// Raw request headers
app.get('/headers', (c) => {
const userAgent = c.req.header('User-Agent')
const contentType = c.req.header('Content-Type')
return c.json({ userAgent, contentType })
})
```
## Route Grouping
Organize routes into groups using sub-applications with the `route()` method or `basePath()`.
```typescript
import { Hono } from 'hono'
// Create sub-applications
const books = new Hono()
books.get('/', (c) => c.json({ books: [] }))
books.get('/:id', (c) => c.json({ id: c.req.param('id') }))
books.post('/', (c) => c.json({ message: 'Book created' }, 201))
const users = new Hono().basePath('/users')
users.get('/', (c) => c.json({ users: [] }))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
users.post('/', (c) => c.json({ message: 'User created' }, 201))
// Main application
const app = new Hono()
// Mount sub-applications
app.route('/books', books) // /books, /books/:id
app.route('/', users) // /users, /users/:id
// API versioning with basePath
const v1 = new Hono().basePath('/api/v1')
v1.get('/status', (c) => c.json({ version: 'v1' }))
const v2 = new Hono().basePath('/api/v2')
v2.get('/status', (c) => c.json({ version: 'v2' }))
app.route('/', v1)
app.route('/', v2)
export default app
```
## Middleware Usage
Apply middleware globally or to specific routes. Middleware executes in registration order with `next()` controlling flow.
```typescript
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { basicAuth } from 'hono/basic-auth'
import { etag } from 'hono/etag'
const app = new Hono()
// Global middleware (all routes)
app.use(logger())
app.use(etag())
// Path-specific middleware
app.use('/api/*', cors())
// Multiple middleware with specific path
app.use(
'/admin/*',
cors({
origin: 'https://admin.example.com',
credentials: true
}),
basicAuth({
username: 'admin',
password: 'secret'
})
)
// Inline custom middleware
app.use(async (c, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
c.header('X-Response-Time', `${ms}ms`)
})
// Middleware on specific route handler
app.get(
'/protected',
basicAuth({ username: 'user', password: 'pass' }),
(c) => c.text('Protected content')
)
app.get('/', (c) => c.text('Hello!'))
app.get('/api/data', (c) => c.json({ data: 'value' }))
app.get('/admin/dashboard', (c) => c.text('Admin Dashboard'))
export default app
```
## Custom Middleware Creation
Create reusable middleware using `createMiddleware` from the factory helper for proper TypeScript types.
```typescript
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'
// Simple middleware
const timing = createMiddleware(async (c, next) => {
const start = Date.now()
await next()
const duration = Date.now() - start
c.res.headers.set('X-Response-Time', `${duration}ms`)
})
// Middleware with parameters
const rateLimit = (limit: number) => {
const requests = new Map()
return createMiddleware(async (c, next) => {
const ip = c.req.header('CF-Connecting-IP') || 'unknown'
const count = requests.get(ip) || 0
if (count >= limit) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
requests.set(ip, count + 1)
await next()
})
}
// Middleware that sets variables
type Variables = {
user: { id: string; name: string }
}
const auth = createMiddleware<{ Variables: Variables }>(async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
return c.json({ error: 'Unauthorized' }, 401)
}
// Validate token and set user
c.set('user', { id: '123', name: 'John' })
await next()
})
const app = new Hono<{ Variables: Variables }>()
app.use(timing)
app.use('/api/*', rateLimit(100))
app.use('/protected/*', auth)
app.get('/protected/profile', (c) => {
const user = c.get('user')
return c.json(user)
})
export default app
```
## Context Variables (set/get)
Share data between middleware and handlers using context variables with type safety.
```typescript
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'
type Variables = {
requestId: string
user: { id: string; role: string } | null
startTime: number
}
const app = new Hono<{ Variables: Variables }>()
// Set variables in middleware
app.use(async (c, next) => {
c.set('requestId', crypto.randomUUID())
c.set('startTime', Date.now())
c.set('user', null)
await next()
})
// Authentication middleware sets user
const authMiddleware = createMiddleware<{ Variables: Variables }>(
async (c, next) => {
const token = c.req.header('Authorization')
if (token === 'Bearer valid-token') {
c.set('user', { id: '1', role: 'admin' })
}
await next()
}
)
app.use('/api/*', authMiddleware)
// Access variables in handlers
app.get('/api/profile', (c) => {
const user = c.get('user')
const requestId = c.get('requestId')
if (!user) {
return c.json({ error: 'Not authenticated', requestId }, 401)
}
return c.json({ user, requestId })
})
// Access via c.var shorthand
app.get('/api/debug', (c) => {
const elapsed = Date.now() - c.var.startTime
return c.json({
requestId: c.var.requestId,
elapsedMs: elapsed,
authenticated: c.var.user !== null
})
})
export default app
```
## Error Handling
Handle errors globally with `app.onError()` and customize 404 responses with `app.notFound()`.
```typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// Custom 404 handler
app.notFound((c) => {
return c.json({
error: 'Not Found',
message: `Route ${c.req.path} does not exist`,
status: 404
}, 404)
})
// Global error handler
app.onError((err, c) => {
console.error(`Error: ${err.message}`)
if (err instanceof HTTPException) {
return c.json({
error: err.message,
status: err.status
}, err.status)
}
return c.json({
error: 'Internal Server Error',
message: err.message,
status: 500
}, 500)
})
// Throw HTTPException for controlled errors
app.get('/item/:id', async (c) => {
const id = c.req.param('id')
const item = await findItem(id)
if (!item) {
throw new HTTPException(404, { message: 'Item not found' })
}
return c.json(item)
})
// Validation error example
app.post('/users', async (c) => {
const body = await c.req.json()
if (!body.email) {
throw new HTTPException(400, { message: 'Email is required' })
}
return c.json({ message: 'User created' }, 201)
})
async function findItem(id: string) {
return id === '1' ? { id, name: 'Item 1' } : null
}
export default app
```
## Request Validation
Validate request data using the built-in validator or third-party libraries like Zod.
```typescript
import { Hono } from 'hono'
import { validator } from 'hono/validator'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
// Manual validation
app.post(
'/posts',
validator('json', (value, c) => {
const { title, body } = value as { title?: string; body?: string }
if (!title || typeof title !== 'string') {
return c.json({ error: 'Title is required' }, 400)
}
if (!body || typeof body !== 'string') {
return c.json({ error: 'Body is required' }, 400)
}
return { title, body }
}),
(c) => {
const { title, body } = c.req.valid('json')
return c.json({ message: 'Created', title, body }, 201)
}
)
// Zod validation
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
age: z.number().min(0).optional()
})
app.post(
'/users',
zValidator('json', createUserSchema),
(c) => {
const user = c.req.valid('json')
// user is fully typed: { email: string; name: string; age?: number }
return c.json({ message: 'User created', user }, 201)
}
)
// Validate multiple targets
const querySchema = z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().default(10)
})
app.get(
'/items/:category',
validator('param', (value, c) => {
const category = value['category']
if (!['books', 'movies', 'music'].includes(category)) {
return c.json({ error: 'Invalid category' }, 400)
}
return { category }
}),
zValidator('query', querySchema),
(c) => {
const { category } = c.req.valid('param')
const { page, limit } = c.req.valid('query')
return c.json({ category, page, limit })
}
)
export default app
```
## CORS Middleware
Configure Cross-Origin Resource Sharing with the built-in CORS middleware.
```typescript
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
// Allow all origins (default)
app.use('/public/*', cors())
// Specific origin
app.use('/api/*', cors({
origin: 'https://example.com',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
exposeHeaders: ['X-Total-Count'],
maxAge: 600,
credentials: true
}))
// Multiple origins
app.use('/shared/*', cors({
origin: ['https://app.example.com', 'https://admin.example.com']
}))
// Dynamic origin based on request
app.use('/dynamic/*', cors({
origin: (origin, c) => {
if (origin.endsWith('.example.com')) {
return origin
}
return 'https://example.com'
}
}))
// Environment-based configuration
app.use('/env/*', async (c, next) => {
const corsMiddleware = cors({
origin: c.env.CORS_ORIGIN || '*'
})
return corsMiddleware(c, next)
})
app.get('/public/data', (c) => c.json({ public: true }))
app.get('/api/data', (c) => c.json({ api: true }))
export default app
```
## JWT Authentication
Protect routes with JWT authentication using the built-in JWT middleware.
```typescript
import { Hono } from 'hono'
import { jwt, sign, verify } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'
type Variables = JwtVariables
const app = new Hono<{ Variables: Variables }>()
const JWT_SECRET = 'your-secret-key'
// Login endpoint - generate token
app.post('/login', async (c) => {
const { username, password } = await c.req.json()
// Validate credentials (simplified)
if (username === 'admin' && password === 'password') {
const payload = {
sub: username,
role: 'admin',
exp: Math.floor(Date.now() / 1000) + 60 * 60 // 1 hour
}
const token = await sign(payload, JWT_SECRET)
return c.json({ token })
}
return c.json({ error: 'Invalid credentials' }, 401)
})
// Protect routes with JWT middleware
app.use('/api/*', jwt({
secret: JWT_SECRET,
alg: 'HS256'
}))
// Access JWT payload in protected routes
app.get('/api/profile', (c) => {
const payload = c.get('jwtPayload')
return c.json({
username: payload.sub,
role: payload.role
})
})
// JWT from cookie instead of header
app.use('/dashboard/*', jwt({
secret: JWT_SECRET,
alg: 'HS256',
cookie: 'auth_token'
}))
// Custom header name
app.use('/custom/*', jwt({
secret: JWT_SECRET,
alg: 'HS256',
headerName: 'X-Auth-Token'
}))
// Using environment variable for secret
app.use('/secure/*', async (c, next) => {
const jwtMiddleware = jwt({
secret: c.env.JWT_SECRET,
alg: 'HS256'
})
return jwtMiddleware(c, next)
})
export default app
```
## Cookie Helper
Manage HTTP cookies with get, set, and delete operations including signed cookies.
```typescript
import { Hono } from 'hono'
import {
getCookie,
setCookie,
deleteCookie,
getSignedCookie,
setSignedCookie
} from 'hono/cookie'
const app = new Hono()
const COOKIE_SECRET = 'super-secret-key'
// Set a cookie
app.post('/login', async (c) => {
const { username } = await c.req.json()
setCookie(c, 'session', username, {
path: '/',
secure: true,
httpOnly: true,
maxAge: 60 * 60 * 24, // 1 day
sameSite: 'Lax'
})
return c.json({ message: 'Logged in' })
})
// Get a cookie
app.get('/profile', (c) => {
const session = getCookie(c, 'session')
if (!session) {
return c.json({ error: 'Not logged in' }, 401)
}
return c.json({ username: session })
})
// Delete a cookie
app.post('/logout', (c) => {
deleteCookie(c, 'session', {
path: '/',
secure: true
})
return c.json({ message: 'Logged out' })
})
// Signed cookies (tamper-proof)
app.post('/set-signed', async (c) => {
await setSignedCookie(c, 'user_data', 'user123', COOKIE_SECRET, {
path: '/',
httpOnly: true
})
return c.json({ message: 'Signed cookie set' })
})
app.get('/get-signed', async (c) => {
const userData = await getSignedCookie(c, COOKIE_SECRET, 'user_data')
if (userData === false) {
return c.json({ error: 'Cookie tampered or invalid' }, 400)
}
return c.json({ userData })
})
// Get all cookies
app.get('/all-cookies', (c) => {
const cookies = getCookie(c)
return c.json(cookies)
})
export default app
```
## Streaming Responses
Stream data to clients using text streams and Server-Sent Events (SSE).
```typescript
import { Hono } from 'hono'
import { stream, streamText, streamSSE } from 'hono/streaming'
const app = new Hono()
// Basic streaming
app.get('/stream', (c) => {
return stream(c, async (stream) => {
stream.onAbort(() => {
console.log('Client disconnected')
})
await stream.write(new Uint8Array([72, 101, 108, 108, 111])) // "Hello"
await stream.write(new Uint8Array([32, 87, 111, 114, 108, 100])) // " World"
})
})
// Text streaming (for AI-like responses)
app.get('/stream-text', (c) => {
return streamText(c, async (stream) => {
const words = ['Hello', 'this', 'is', 'a', 'streaming', 'response']
for (const word of words) {
await stream.write(word + ' ')
await stream.sleep(200) // Simulate delay
}
await stream.writeln('') // New line at end
})
})
// Server-Sent Events
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: JSON.stringify({
time: new Date().toISOString(),
count: id
}),
event: 'update',
id: String(id++)
})
await stream.sleep(1000)
}
})
})
// Stream with error handling
app.get('/stream-safe', (c) => {
return stream(
c,
async (stream) => {
await stream.write(new Uint8Array([1, 2, 3]))
// Potentially failing operation
throw new Error('Something went wrong')
},
(err, stream) => {
console.error('Stream error:', err)
stream.writeln('Error occurred during streaming')
}
)
})
export default app
```
## JSX Server-Side Rendering
Render HTML using JSX syntax for server-side rendering.
```tsx
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
// Layout component
const Layout: FC<{ title: string }> = ({ title, children }) => (
{title}
{children}
)
// Page component
const HomePage: FC<{ name: string }> = ({ name }) => (
Welcome, {name}!
This is rendered with Hono JSX.
)
// List component
const UserList: FC<{ users: string[] }> = ({ users }) => (
User List
{users.map((user) => (
- {user}
))}
)
app.get('/', (c) => {
return c.html()
})
app.get('/users', (c) => {
const users = ['Alice', 'Bob', 'Charlie']
return c.html()
})
// Async component
const AsyncData: FC = async () => {
await new Promise((r) => setTimeout(r, 100))
const data = { message: 'Loaded!' }
return {data.message}
}
app.get('/async', (c) => {
return c.html(
)
})
export default app
```
## RPC Client (Type-Safe API Calls)
Share API types between server and client for end-to-end type safety using Hono's RPC feature.
```typescript
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const postSchema = z.object({
title: z.string(),
body: z.string()
})
const routes = app
.get('/posts', (c) => {
return c.json({
posts: [
{ id: 1, title: 'Hello' },
{ id: 2, title: 'World' }
]
})
})
.get('/posts/:id', (c) => {
const id = c.req.param('id')
return c.json({ id, title: `Post ${id}` })
})
.post('/posts', zValidator('json', postSchema), (c) => {
const { title, body } = c.req.valid('json')
return c.json({ id: 3, title, body }, 201)
})
export type AppType = typeof routes
export default app
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
import type { InferRequestType, InferResponseType } from 'hono/client'
const client = hc('http://localhost:8787')
// GET request - TypeScript knows the response shape
async function getPosts() {
const res = await client.posts.$get()
if (res.ok) {
const data = await res.json()
console.log(data.posts) // { id: number; title: string }[]
}
}
// GET with path parameter
async function getPost(id: string) {
const res = await client.posts[':id'].$get({
param: { id }
})
if (res.ok) {
const data = await res.json()
console.log(data.title)
}
}
// POST with typed body
async function createPost(title: string, body: string) {
const res = await client.posts.$post({
json: { title, body }
})
if (res.ok) {
const data = await res.json()
console.log(data.id) // TypeScript knows this is number
}
}
// Infer request/response types
type PostRequest = InferRequestType['json']
type PostResponse = InferResponseType
```
## Testing with app.request()
Test Hono applications using the built-in `app.request()` method without starting a server.
```typescript
import { Hono } from 'hono'
import { describe, it, expect } from 'vitest'
const app = new Hono()
app.get('/hello', (c) => c.text('Hello World'))
app.get('/json', (c) => c.json({ message: 'Hello' }))
app.post('/echo', async (c) => {
const body = await c.req.json()
return c.json(body, 201)
})
app.get('/user/:id', (c) => {
return c.json({ id: c.req.param('id') })
})
describe('Hono App', () => {
it('GET /hello returns text', async () => {
const res = await app.request('/hello')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello World')
})
it('GET /json returns JSON', async () => {
const res = await app.request('/json')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ message: 'Hello' })
})
it('POST /echo returns body', async () => {
const res = await app.request('/echo', {
method: 'POST',
body: JSON.stringify({ test: 'data' }),
headers: { 'Content-Type': 'application/json' }
})
expect(res.status).toBe(201)
expect(await res.json()).toEqual({ test: 'data' })
})
it('GET /user/:id returns param', async () => {
const res = await app.request('/user/123')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ id: '123' })
})
// Test with mock environment
it('works with env bindings', async () => {
const appWithEnv = new Hono<{ Bindings: { API_KEY: string } }>()
appWithEnv.get('/key', (c) => c.text(c.env.API_KEY))
const res = await appWithEnv.request('/key', {}, { API_KEY: 'secret' })
expect(await res.text()).toBe('secret')
})
})
```
## Cloudflare Workers Bindings
Access Cloudflare Workers environment bindings (KV, R2, D1, Durable Objects) with proper TypeScript types.
```typescript
import { Hono } from 'hono'
type Bindings = {
// Environment variables
API_KEY: string
ENVIRONMENT: string
// KV Namespace
MY_KV: KVNamespace
// R2 Bucket
MY_BUCKET: R2Bucket
// D1 Database
MY_DB: D1Database
}
const app = new Hono<{ Bindings: Bindings }>()
// Access environment variables
app.get('/config', (c) => {
return c.json({
environment: c.env.ENVIRONMENT,
hasApiKey: !!c.env.API_KEY
})
})
// KV operations
app.get('/kv/:key', async (c) => {
const key = c.req.param('key')
const value = await c.env.MY_KV.get(key)
if (!value) {
return c.json({ error: 'Not found' }, 404)
}
return c.json({ key, value })
})
app.put('/kv/:key', async (c) => {
const key = c.req.param('key')
const { value } = await c.req.json()
await c.env.MY_KV.put(key, value)
return c.json({ message: 'Saved' })
})
// R2 operations
app.get('/files/:key', async (c) => {
const key = c.req.param('key')
const object = await c.env.MY_BUCKET.get(key)
if (!object) {
return c.json({ error: 'File not found' }, 404)
}
return new Response(object.body, {
headers: { 'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream' }
})
})
app.put('/files/:key', async (c) => {
const key = c.req.param('key')
const body = await c.req.arrayBuffer()
await c.env.MY_BUCKET.put(key, body)
return c.json({ message: 'Uploaded' })
})
// D1 queries
app.get('/users', async (c) => {
const { results } = await c.env.MY_DB
.prepare('SELECT * FROM users LIMIT 10')
.all()
return c.json({ users: results })
})
app.post('/users', async (c) => {
const { name, email } = await c.req.json()
await c.env.MY_DB
.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(name, email)
.run()
return c.json({ message: 'User created' }, 201)
})
export default app
```
## Factory Helper
Use the factory helper to create middleware and handlers with shared type definitions.
```typescript
import { Hono } from 'hono'
import { createFactory, createMiddleware } from 'hono/factory'
// Define shared types
type Env = {
Bindings: {
DATABASE_URL: string
}
Variables: {
db: Database
user: User | null
requestId: string
}
}
interface Database {
query: (sql: string) => Promise
}
interface User {
id: string
name: string
}
// Create factory with types
const factory = createFactory()
// Create typed middleware
const dbMiddleware = factory.createMiddleware(async (c, next) => {
const db: Database = {
query: async (sql) => ({ rows: [] })
}
c.set('db', db)
await next()
})
const authMiddleware = factory.createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')
if (token === 'Bearer valid') {
c.set('user', { id: '1', name: 'John' })
} else {
c.set('user', null)
}
await next()
})
const requestIdMiddleware = factory.createMiddleware(async (c, next) => {
c.set('requestId', crypto.randomUUID())
await next()
})
// Create handlers with proper types
const getUsers = factory.createHandlers(
dbMiddleware,
async (c) => {
const result = await c.var.db.query('SELECT * FROM users')
return c.json({ users: result.rows, requestId: c.var.requestId })
}
)
const getProfile = factory.createHandlers(
authMiddleware,
(c) => {
const user = c.var.user
if (!user) {
return c.json({ error: 'Unauthorized' }, 401)
}
return c.json(user)
}
)
// Create app from factory
const app = factory.createApp()
app.use(requestIdMiddleware)
app.get('/users', ...getUsers)
app.get('/profile', ...getProfile)
export default app
```
## Summary
Hono excels at building high-performance web APIs, edge applications, and full-stack TypeScript applications. Its lightweight design and Web Standards compliance make it ideal for serverless environments like Cloudflare Workers, Vercel Edge Functions, and AWS Lambda@Edge. The framework's middleware system follows familiar patterns from Express while providing full TypeScript inference, making it easy to compose authentication, validation, logging, and CORS handling.
The RPC feature combined with Zod validation enables end-to-end type safety between server and client code, eliminating runtime type mismatches. Hono's JSX support allows server-side rendering without additional dependencies, while the streaming helpers enable real-time features like Server-Sent Events. Whether building a simple API proxy, a complex microservice, or a full-stack application with client-side type safety, Hono provides the performance and developer experience needed for modern web development.