| name | hono-api |
| description | Use when building APIs with Hono framework on Bun, Deno, Node.js, Cloudflare Workers, or Vercel Edge. Covers routing, middleware, validation with Zod, RPC client, and deployment patterns. |
Hono API
Setup
bun create hono@latest my-app
bun add hono
bun add hono @hono/node-server
Basic App
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { prettyJSON } from 'hono/pretty-json'
const app = new Hono()
app.use('*', logger())
app.use('*', cors())
app.use('*', prettyJSON())
app.get('/', (c) => c.text('Hello Hono!'))
app.get('/health', (c) => c.json({ status: 'ok' }))
export default app
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000 })
Routing
const app = new Hono()
app.get('/users', handler)
app.post('/users', handler)
app.put('/users/:id', handler)
app.patch('/users/:id', handler)
app.delete('/users/:id', handler)
app.get('/users/:id', (c) => {
const id = c.req.param('id')
const page = c.req.query('page') ?? '1'
return c.json({ id, page })
})
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
const api = new Hono().basePath('/api')
const users = new Hono()
users.get('/', listUsers)
users.post('/', createUser)
users.get('/:id', getUser)
api.route('/users', users)
app.route('/', api)
Validation with Zod Validator
bun add @hono/zod-validator zod
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(0).optional(),
})
app.post('/users',
zValidator('json', createUserSchema),
async (c) => {
const data = c.req.valid('json')
return c.json({ id: 1, ...data }, 201)
}
)
const querySchema = z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().max(100).default(20),
})
app.get('/users', zValidator('query', querySchema), (c) => {
const { page, limit } = c.req.valid('query')
return c.json({ page, limit })
})
Middleware
import { createMiddleware } from 'hono/factory'
import { HTTPException } from 'hono/http-exception'
const authMiddleware = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) throw new HTTPException(401, { message: 'Unauthorized' })
const user = await verifyToken(token)
c.set('user', user)
await next()
})
app.use('/api/*', authMiddleware)
app.get('/api/me', (c) => {
const user = c.get('user')
return c.json(user)
})
RPC Client (type-safe)
const route = app.get('/users/:id', zValidator('param', z.object({ id: z.string() })), (c) => {
return c.json({ id: c.req.valid('param').id, name: 'Alice' })
})
export type AppType = typeof route
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:3000')
const res = await client.users[':id'].$get({ param: { id: '1' } })
const data = await res.json()
Error Handling
import { HTTPException } from 'hono/http-exception'
app.get('/users/:id', async (c) => {
const user = await db.findUser(c.req.param('id'))
if (!user) throw new HTTPException(404, { message: 'User not found' })
return c.json(user)
})
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error(err)
return c.json({ error: 'Internal Server Error' }, 500)
})
app.notFound((c) => c.json({ error: 'Not found' }, 404))
Cloudflare Workers
export default app
app.get('/kv', async (c) => {
const value = await c.env.MY_KV.get('key')
return c.json({ value })
})
Context Variables (Type-safe)
type Variables = {
user: { id: string; email: string }
}
const app = new Hono<{ Variables: Variables }>()