← Back to articles

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.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.