Hono
Hono is an ultra-fast web framework built on Web Standards (Request/Response, URL). It runs unchanged on Node, Bun, Deno, Cloudflare Workers, Vercel Edge, Lambda, and Fastly. The trade is a tiny core plus a router built for V8-friendly hot paths — making it the right pick for serverless and edge.
A typed Hono app with middleware, validation, and zod
EXAMPLE
// npm i hono @hono/node-server zod @hono/zod-validator
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { secureHeaders } from 'hono/secure-headers';
import { HTTPException } from 'hono/http-exception';
import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';
type Bindings = { DATABASE_URL: string }; // env values (Workers / Cloudflare style)
type Variables = { reqId: string }; // request-scoped context
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();
// 1) Global middleware — runs in declaration order
app.use('*', logger());
app.use('*', secureHeaders());
app.use('*', cors({ origin: ['https://app.example.com'], credentials: true }));
// 2) Per-request id available everywhere
app.use('*', async (c, next) => {
c.set('reqId', crypto.randomUUID());
c.header('x-request-id', c.get('reqId'));
await next();
});
// 3) Auth as a scoped middleware
const auth = async (c: any, next: any) => {
const token = c.req.header('authorization')?.replace(/^Bearer /, '');
if (!token) throw new HTTPException(401, { message: 'missing token' });
c.set('user', await verify(token));
await next();
};
// 4) Typed input validation with Zod
const createOrder = z.object({
customer: z.string().min(1).max(120),
totalCents: z.number().int().nonnegative(),
notes: z.string().max(500).optional(),
});
app.post('/api/orders',
auth,
zValidator('json', createOrder),
async (c) => {
const body = c.req.valid('json'); // ← fully typed
const id = crypto.randomUUID();
await c.env.DATABASE_URL; // (use Bindings here)
return c.json({ id, ...body, status: 'new', createdAt: new Date().toISOString() }, 201);
},
);
// 5) Path params with regex constraint
app.get('/api/orders/:id{[a-f0-9-]{8,}}', auth, async (c) => {
const id = c.req.param('id');
return c.json({ id, customer: 'alice', totalCents: 4995 });
});
// 6) Streaming + Server-Sent Events
app.get('/stream', (c) => {
return c.streamSSE(async (sse) => {
for (let i = 0; i < 5; i++) {
await sse.writeSSE({ event: 'tick', data: String(i) });
await sleep(500);
}
});
});
// 7) Centralised error -> JSON
app.onError((err, c) => {
if (err instanceof HTTPException) return err.getResponse();
console.error({ reqId: c.get('reqId'), err });
return c.json({ error: 'internal' }, 500);
});
app.notFound((c) => c.json({ error: 'not found' }, 404));
// 8) Adapters — same app, different runtimes
// Node:
serve({ fetch: app.fetch, port: 3000 });
// Cloudflare Workers:
// export default app;
// Bun:
// export default { fetch: app.fetch };
// Vercel Edge:
// export const config = { runtime: 'edge' }; export default app.fetch;
function sleep(ms: number) { return new Promise((r) => setTimeout(r, ms)); }
async function verify(_token: string) { return { id: 'u1' }; }
Why it matters
Hono really shines at the edge because the same handler runs on a Cloudflare Worker, Lambda, and your laptop without code changes. Build for Web standards (Request/Response) and you stop locking into a runtime — the deploy decision becomes a config flip rather than a rewrite.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { Hono } from 'hono';
const app = new Hono();
app.get('/', c => c.text('Hello!'));
export default app;
Try it Yourself »
Discussion
Loading…