How to Use Hono: The Beginner's Guide (First App, Routes, Middleware, Validation, and Deploy)

CodexFuse Team · 2026-10-31T03:57:27.257Z · 15 min read

What is Hono?

Hono is a small, ultrafast JavaScript web framework for building APIs and web apps that run unchanged on Node.js, Bun, Deno, Cloudflare Workers, and other runtimes. Learning how to use Hono means learning one tiny API — built entirely on the web standards Request and Response — and deploying the same code everywhere.

The name means "flame" in Japanese, and the framework is built around a single rule: if a runtime speaks the Fetch API, Hono runs on it (hono.dev getting-started guide, retrieved Oct 30, 2026). That is the whole trick — your routes, middleware, and handlers never touch runtime-specific APIs, so one codebase ships to a traditional server and to the edge with only the entry point changing. As you build the endpoints in this guide, you can test every one with a free REST API tester instead of wiring up a frontend first.

Hono vs Express: which should a beginner pick?

If you already know Express, pick Hono when you want your code to run on more than Node.js; stick with Express only if your project will never leave Node.js and you depend on its huge middleware ecosystem. Hono's API deliberately mirrors Express — app.get, app.post, route parameters with a colon — so an Express developer reads Hono code with zero learning curve.

The real difference is portability. Express is welded to Node.js; Hono is welded to web standards, so the app you write locally on Node runs on Cloudflare Workers, Deno Deploy, Bun, AWS Lambda, Vercel, Fastly, and Netlify without rewrites (eliteai.tools Hono skill reference, retrieved Oct 30, 2026). If you are still fuzzy on the underlying concept, read what an API actually is first — everything in this guide assumes you know what a request and a response are.

What do you need before you start?

You need Node.js 18 or newer and a package manager you already use — npm, pnpm, yarn, or Bun all work with the Hono starter. The framework itself is written in TypeScript, but you can write your app in plain JavaScript if you prefer; TypeScript just unlocks the RPC feature later.

You also need to decide where your first app will run. For this guide you will start on Node.js (the most familiar path), then deploy the same code to Cloudflare Workers, because that two-runtime arc is exactly what Hono exists for. No Cloudflare account is needed for local development.

How do you create your first Hono app?

You create it with one command that asks which runtime template you want, installs nothing globally, and leaves you with a runnable project in under a minute. Here is the exact sequence:

  1. Run npm create hono@latest my-api (swap npm for pnpm, yarn, or bun if that is your package manager).
  2. Pick a template from the prompt. As of the official docs updated October 30, 2026, the list is aws-lambda, bun, cloudflare-pages, cloudflare-workers, deno, fastly, nextjs, nodejs, and vercel (hono.dev getting-started guide).
  3. cd my-api and install dependencies with npm install.
  4. Start the local server with npm run dev and open the printed URL in your browser.

For this guide, choose the nodejs template. You will see src/index.ts containing the smallest useful Hono program:

import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

serve({ fetch: app.fetch, port: 3000 })

Everything except the last line is identical on every runtime — that last line is the only runtime-specific part, and the starter template writes it for you. If you ever add dependencies by hand and want clean scripts without memorizing npm syntax, a package.json generator keeps the manifest tidy.

How does the Hono context object (c) work?

The context object c is the single thing every handler receives, and it wraps the incoming request, the outgoing response, and shared state. If you learn the dozen methods on c, you know most of Hono.

Handlers never touch the raw Node request or the Workers request directly — c.req gives you a standards-based view of the request, and the c.text() / c.json() / c.html() helpers build standards-based responses. Here are the methods you will use constantly, all documented in the official getting-started guide:

MethodWhat it doesExample
c.req.param('id')Reads a route parameter like /posts/:id/posts/42 gives '42'
c.req.query('page')Reads a URL query string value?page=2 gives '2'
c.req.json()Parses a JSON request body (async)const body = await c.req.json()
c.req.header('x-token')Reads one request headerAuth tokens, content types
c.text('hi', 201)Returns a plain-text response with a status code201 for created resources
c.json({ ok: true })Returns a JSON responseThe default for API endpoints
c.html('<h1>Hi</h1>')Returns an HTML responseSimple server-rendered pages
c.header('X-Api', 'v1')Adds a response header before returningCustom metadata on responses
c.get('jwtPayload')Reads data set by earlier middlewareThe verified JWT payload (see auth section)
c.env.JWT_SECRETReads runtime bindings / environmentSecrets without hardcoding them

How do you add routes and route parameters in Hono?

You chain HTTP-method calls on the app, with the path and handler as arguments, exactly like Express. Parameters use a colon, and the handler pulls them out with c.req.param():

app.get('/posts', (c) => c.json({ posts: [] }))

app.get('/posts/:id', (c) => {
  const id = c.req.param('id')
  const page = c.req.query('page') ?? '1'
  return c.json({ id, page })
})

app.post('/posts', (c) => c.text('Created!', 201))

app.delete('/posts/:id', (c) =>
  c.text(`${c.req.param('id')} is deleted!`)
)

Notice the status code rides along as the second argument — c.text('Created!', 201) — instead of a separate method call. Routes match in the order you register them, and Hono's router is one of the fastest available for JavaScript, which is part of why the framework markets itself as ultrafast (eliteai.tools Hono skill reference, retrieved Oct 30, 2026).

How do you use middleware in Hono?

You register middleware with app.use() before the routes it should protect, and Hono ships the common ones built in — no extra installs. The three you will reach for first are logger, cors, and a custom middleware you write yourself:

import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'

const app = new Hono()

app.use('*', logger())
app.use('/api/*', cors({ origin: 'https://myapp.com' }))

// Your own middleware: runs before the handler, then calls next()
app.use('/api/*', async (c, next) => {
  const started = Date.now()
  await next()
  console.log(`${c.req.method} ${c.req.path} took ${Date.now() - started}ms`)
})

The pattern is always the same: middleware runs, calls await next() to hand off to the next middleware or the route, and can then touch the response on the way back out. Other built-ins in the box include jwt, basicAuth, bearerAuth, compress, etag, csrf, bodyLimit, timeout, prettyJSON, and secureHeaders (hono.dev getting-started guide). When you start testing protected endpoints, step through them in the Postman beginner guide workflow — Postman and the REST tester both speak plain HTTP, so your Hono routes do not care which client you use.

How do you validate request data with Zod in Hono?

You install the official Zod adapter, declare a schema, and drop zValidator in front of the handler — the validated data arrives via c.req.valid(), fully typed. Never trust a request body without validating it first.

npm i @hono/zod-validator
npm i zod
import { zValidator } from '@hono/zod-validator'
import * as z from 'zod'

const noteSchema = z.object({
  title: z.string().min(1).max(120),
  body: z.string().max(5000),
})

app.post(
  '/notes',
  zValidator('json', noteSchema),
  (c) => {
    const { title, body } = c.req.valid('json')
    return c.json({ id: 1, title, body }, 201)
  }
)

The first argument to zValidator is the validation target — json, query, header, param, cookie, or form — and invalid input gets a 400 response before your handler ever runs (hono.dev validation guide, retrieved Oct 30, 2026). Because the schema is real TypeScript, c.req.valid('json') already knows the shape of the data, so there is nothing left to cast by hand.

How do you add JWT authentication in Hono?

You use the JWT middleware exactly as Hono ships it: sign() mints tokens on your login route, and jwt() guards the protected routes. Do not hand-roll token checks — the middleware already handles the header parsing, signature verification, and expiry.

import { Hono } from 'hono'
import { jwt, sign } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'

const SECRET = 'use-a-long-random-secret-in-production'
type Variables = JwtVariables
const app = new Hono<{ Variables: Variables }>()

// Public: exchange credentials for a token
app.post('/login', async (c) => {
  const { username, password } = await c.req.json()
  if (username !== 'ada' || password !== 'correct-horse') {
    return c.text('Invalid credentials', 401)
  }
  const payload = {
    sub: username,
    exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour
  }
  const token = await sign(payload, SECRET)
  return c.json({ token })
})

// Protected: everything under /notes/* needs a valid token
app.use('/notes/*', jwt({ secret: SECRET, alg: 'HS256' }))

app.get('/notes', (c) => {
  const payload = c.get('jwtPayload') // typed via JwtVariables
  return c.json({ notes: [], owner: payload.sub })
})

Three details matter. First, jwt() requires both secret and alg — supported algorithms include HS256/384/512, the RS/PS/ES families, and EdDSA (hono.dev JWT middleware docs, updated October 2026). Second, in production read the secret from the environment instead of a constant: jwt({ secret: c.env.JWT_SECRET, alg: 'HS256' }) inside a small wrapper middleware. Third, the decoded payload lands in the context as jwtPayload, and typing your app with JwtVariables makes c.get('jwtPayload') type-safe.

What is the Hono RPC feature, and how do you use it?

RPC lets your frontend call the API with full type safety by importing the server's route types — no OpenAPI file, no codegen step, no hand-written client types. It is the single biggest reason TypeScript developers choose Hono over Express.

On the server you export the route's type; on the client you feed that type to hc:

// server: src/index.ts
const route = app.get('/notes/:id', (c) => {
  return c.json({ id: c.req.param('id'), title: 'Hello' })
})
export type AppType = typeof route

// client: any TypeScript frontend
import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:3000')
const res = await client.notes[':id'].$get({ param: { id: '1' } })
const note = await res.json() // typed as { id: string; title: string }

If you rename a route parameter or change the response shape on the server, the client code fails to compile — the API contract is enforced by the compiler instead of by documentation discipline (dev.to Hono guide, retrieved Oct 30, 2026). The JSON shapes flowing through RPC are ordinary TypeScript types, so a JSON to TypeScript converter is handy when you are sketching response shapes from real API output before you formalize them.

Hono on every runtime: the one-table comparison

The same app code runs everywhere — only the scaffold template, the dev command, and the final serve line change. Here is the full picture in one table, assembled from the official getting-started guides (all retrieved Oct 30, 2026):

RuntimeTemplateLocal devHow the app startsDeploy
Node.jsnodejsnpm run dev → localhost:3000serve({ fetch: app.fetch, port: 3000 }) from @hono/node-serverAny VPS or container, like any Node app
Bunbunbun run devexport default { port: 3000, fetch: app.fetch }Bun runtime host
Denodenodeno task devDeno.serve(app.fetch)Deno Deploy
Cloudflare Workerscloudflare-workersnpm run dev → localhost:8787export default appnpm run deploy (Wrangler)
AWS Lambdaaws-lambdanpm run devexport default app via the Lambda adapterAWS Lambda

The takeaway for beginners: write and debug on Node.js where your tooling is familiar, then point the same repository at Workers or Deno Deploy when you want the edge. Porting is a template swap, not a rewrite.

How do you deploy a Hono app to Cloudflare Workers?

You scaffold with the cloudflare-workers template, develop locally with Wrangler, and deploy with a single command. First create the project exactly as shown earlier, choosing cloudflare-workers at the template prompt, then:

  1. Run npm run dev — Wrangler serves your app at http://localhost:8787 with the real Workers runtime behavior.
  2. Build your routes, middleware, validation, and auth exactly as in this guide — none of it changes.
  3. Run npm run deploy to ship to Cloudflare's edge network worldwide.

One Workers-specific rule to respect: there is no Node.js standard library on the edge, so do not import node:fs or node:crypto directly — use the web-standard equivalents (fetch, crypto.subtle) and Hono code stays portable. If the Workers dashboard and its terminology are new to you, the Cloudflare beginner guide walks through the account and dashboard side of this deploy.

How do you deploy a Hono app on Node.js?

You add the Node adapter package and hand your app's fetch handler to its serve() function — that is the entire deployment story. The official Node.js guide (updated October 2026) gives this exact pattern:

npm install @hono/node-server
import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hono meets Node.js'))

serve({ fetch: app.fetch, port: 3000 })

The adapter needs Node.js 18 or newer (18.14.1+ for the 18 line) because it leans on the web-standard APIs Node added in that era (hono.dev Node.js guide). serve() wraps node:http and returns the underlying server instance, so you keep the usual Node deploy playbook: a process manager, a reverse proxy, and environment variables for secrets. Everything else — your routes, your Zod schemas, your JWT middleware — is byte-for-byte the same code that runs on Workers.

What are Hono's honest limits?

Hono is deliberately small, and small means gaps. There is no built-in ORM, no session store, no template engine opinion, and no admin panel — you assemble those from packages, the same as Express. The middleware ecosystem is also younger than Express's, so an exotic integration you need might not exist yet as a Hono package.

The edge story has real constraints too: Workers and other edge runtimes cap CPU time per request and forbid long-lived connections, so Hono will not fix an app that needs WebSockets held open for hours or heavy synchronous computation. And while Hono runs on Node.js through an adapter, that adapter adds overhead the edge runtimes do not have — if Node.js is your only target forever, the portability you are paying for buys you nothing. Choose the tool for the deployment shape you actually have.

Sources

  • Hono official docs, "Getting Started" (hono.dev, page updated ~16 hours before retrieval, retrieved Oct 30, 2026) — npm create hono@latest flow, the nine current templates (aws-lambda, bun, cloudflare-pages, cloudflare-workers, deno, fastly, nextjs, nodejs, vercel), Hello World, c.req.param / c.req.query / c.text / c.json, and the built-in middleware list.
  • Hono official docs, "Validation" (hono.dev, retrieved Oct 30, 2026) — npm i @hono/zod-validator, zValidator('json', schema), c.req.valid('json'), and the six validation targets (json, query, header, param, cookie, form).
  • Hono official docs, "JWT Auth Middleware" (hono.dev, updated ~17 days before retrieval, retrieved Oct 30, 2026) — import { jwt } from 'hono/jwt', required secret and alg options, c.get('jwtPayload') with JwtVariables typing, c.env.JWT_SECRET usage, and the supported algorithm list.
  • Hono official docs, "JWT Authentication Helper" (hono.dev, updated ~17 days before retrieval, retrieved Oct 30, 2026) — sign(payload, secret), verify(token, secret, alg), and decode(token) from hono/jwt.
  • Hono official docs, "Node.js" (hono.dev, page updated ~16 hours before retrieval, retrieved Oct 30, 2026) — npm install @hono/node-server, serve({ fetch: app.fetch, port: 3000 }), and the Node 18.14.1+ version floor.
  • EliteAI Tools, "Hono — AI Agent skill" (eliteai.tools, retrieved Oct 30, 2026) — multi-runtime list (Cloudflare Workers, Deno Deploy, Bun, Node.js, AWS Lambda, Vercel, Netlify, Fastly), the Bun export default { port, fetch } entry shape, and the hc() RPC client example.
  • dev.to, "Hono Has a Free Web Framework — Ultrafast APIs on Any Runtime in 5 Minutes" (retrieved Oct 30, 2026) — Hello World in four lines, c.req.param('id') routing, built-in middleware imports, and type AppType = typeof route for RPC.
  • Flavio Copes, "Hono" (flaviocopes.com, retrieved Oct 30, 2026) — the multi-runtime pitch, routes as Fetch API handlers, and where the guide stops (first routes).

Frequently asked questions

Is Hono better than Express for beginners?

For learning, they are equally easy — Hono copies Express's routing style on purpose. Choose Hono if you might deploy to the edge or want TypeScript RPC; choose Express if you need its enormous existing middleware ecosystem and will only ever run on Node.js.

Do I need TypeScript to use Hono?

No. Hono works in plain JavaScript, and every example in this guide runs if you strip the type annotations. TypeScript only unlocks the RPC client types and editor autocomplete — nice to have, not required.

Can I use Hono with a database?

Yes. Hono has no built-in ORM, so you bring your own: Drizzle, Prisma, or Kysely all work, and on the edge you would pair them with a serverless-friendly database driver. The Zod validation section in this guide is the natural place to validate rows before they reach the database.

How is Hono different from Next.js API routes?

Next.js API routes live inside a Next.js app and deploy with it; Hono is a standalone framework whose app deploys to any runtime independently. Use Hono when the API is its own service, and Next.js API routes when the API exists only to serve one Next.js frontend.

Does Hono work on Cloudflare Workers' free plan?

Hono itself has no plan requirements — it is just a library. Whether your app fits the free tier depends on Cloudflare's limits for requests per day and CPU time per request, so keep handlers light and check the current Workers pricing page before relying on exact numbers.

Featured on Twelve Tools Launch Llama Tools Launch your MCP