iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…