How to Build an API with Hono (2026 Guide)
Hono is a lightweight, ultrafast web framework that runs everywhere — Cloudflare Workers, Bun, Deno, Node.js, and Vercel Edge. It's the Express replacement developers have been waiting for. Here's how to build a production API with it.
Why Hono
- Fast — 3x faster than Express on benchmarks
- Tiny — 14KB core (Express: 200KB+)
- Type-safe — TypeScript-first with Zod integration
- Universal — same code runs on any runtime
- Modern — Web Standards API (Request/Response), no legacy baggage
Setup
npm create hono@latest my-api
cd my-api
npm install
npm run dev # http://localhost:3000
Basic Routing
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json({ message: 'Hello Hono!' }))
app.get('/users', (c) => c.json({ users: [] }))
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ id, name: 'John' })
})
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
app.delete('/users/:id', (c) => {
return c.json({ deleted: c.req.param('id') })
})
export default app
Validation with Zod
npm install zod @hono/zod-validator
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'user']).default('user'),
})
app.post(
'/users',
zValidator('json', createUserSchema),
async (c) => {
const data = c.req.valid('json') // Fully typed!
// data.name, data.email, data.role are all typed
return c.json({ user: data }, 201)
}
)
Validation errors return 400 with details automatically.
Middleware
Built-in Middleware
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'
import { secureHeaders } from 'hono/secure-headers'
app.use('*', logger())
app.use('*', secureHeaders())
app.use('*', cors({ origin: 'https://yourapp.com' }))
app.use('*', prettyJSON())
Custom Auth Middleware
import { createMiddleware } from 'hono/factory'
const authMiddleware = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return c.json({ error: 'Unauthorized' }, 401)
}
const user = await verifyToken(token)
if (!user) {
return c.json({ error: 'Invalid token' }, 401)
}
c.set('user', user)
await next()
})
// Apply to protected routes
app.use('/api/*', authMiddleware)
Database Integration
With Drizzle + Neon
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import * as schema from './db/schema'
const sql = neon(process.env.DATABASE_URL!)
const db = drizzle(sql, { schema })
app.get('/users', async (c) => {
const users = await db.select().from(schema.users)
return c.json({ users })
})
app.get('/users/:id', async (c) => {
const user = await db.query.users.findFirst({
where: eq(schema.users.id, c.req.param('id')),
})
if (!user) return c.json({ error: 'Not found' }, 404)
return c.json({ user })
})
Route Groups
const api = new Hono()
// Public routes
api.get('/health', (c) => c.json({ status: 'ok' }))
// Protected routes
const protected = new Hono()
protected.use('*', authMiddleware)
protected.get('/users', listUsers)
protected.post('/users', createUser)
protected.get('/users/:id', getUser)
api.route('/v1', protected)
app.route('/api', api)
// Routes:
// GET /api/health (public)
// GET /api/v1/users (protected)
// POST /api/v1/users (protected)
// GET /api/v1/users/:id (protected)
Error Handling
import { HTTPException } from 'hono/http-exception'
// Throw errors anywhere
app.get('/users/:id', async (c) => {
const user = await findUser(c.req.param('id'))
if (!user) {
throw new HTTPException(404, { message: 'User not found' })
}
return c.json({ user })
})
// Global error handler
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)
})
// 404 handler
app.notFound((c) => c.json({ error: 'Not Found' }, 404))
Rate Limiting
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(100, '60 s'),
})
app.use('/api/*', async (c, next) => {
const ip = c.req.header('CF-Connecting-IP') || 'unknown'
const { success, remaining } = await ratelimit.limit(ip)
c.header('X-RateLimit-Remaining', remaining.toString())
if (!success) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
await next()
})
OpenAPI Documentation
npm install @hono/zod-openapi @hono/swagger-ui
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import { swaggerUI } from '@hono/swagger-ui'
const app = new OpenAPIHono()
const getUserRoute = createRoute({
method: 'get',
path: '/users/{id}',
request: {
params: z.object({ id: z.string() }),
},
responses: {
200: {
content: { 'application/json': { schema: userSchema } },
description: 'User found',
},
},
})
app.openapi(getUserRoute, async (c) => {
const { id } = c.req.valid('param')
return c.json({ id, name: 'John' })
})
// Swagger UI at /docs
app.get('/docs', swaggerUI({ url: '/doc' }))
app.doc('/doc', { openapi: '3.0.0', info: { title: 'My API', version: '1.0' } })
Deployment
Cloudflare Workers
npx wrangler deploy
Bun
bun run src/index.ts
Node.js
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000 })
Vercel Edge
// api/[[...route]].ts
export const config = { runtime: 'edge' }
export default app.fetch
FAQ
Hono vs Express?
Hono is faster, smaller, type-safe, and runs on edge runtimes. Express has a larger ecosystem and more middleware. For new projects, use Hono.
Is Hono production-ready?
Yes. Used by Cloudflare, Deno, and many companies in production.
Can I use Hono with Next.js?
Yes, as API routes. But for a full Next.js app, the built-in API routes are simpler.
Should I use Hono or tRPC?
tRPC for full-stack TypeScript apps (client + server in one repo). Hono for standalone APIs consumed by multiple clients.
Bottom Line
Hono is the best framework for building APIs in 2026. Fast, type-safe, runs everywhere. Use it for standalone APIs, microservices, or edge functions. Setup takes minutes, and the developer experience is excellent.