Files
2nd/10_Wiki/Topics/Coding/Backend_Hono_Middleware_Deep.md
T
2026-05-10 22:08:15 +09:00

8.2 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
backend-hono-middleware-deep Hono Middleware — modern Express alternative Coding draft B conceptual 2026-05-09 2026-05-09
backend
hono
vibe-coding
language applicable_to
TS
Backend
Hono
middleware
Cloudflare Workers
Deno
Bun
edge-friendly

Hono Middleware Deep

Modern Express alternative. Edge-friendly (CF Workers / Deno / Bun / Node), TypeScript-first, 작은 bundle.

📖 핵심 개념

  • Web standards (Fetch, Request, Response).
  • 매 runtime (Node / Bun / Deno / CF / Lambda).
  • Type-safe routing.
  • Built-in middleware.

💻 코드 패턴

Basic

import { Hono } from 'hono';

const app = new Hono();

app.get('/', (c) => c.text('Hello'));
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }));

// Cloudflare Worker
export default app;

// Bun
Bun.serve({ fetch: app.fetch });

// Node
import { serve } from '@hono/node-server';
serve(app);

→ 같은 code, 다른 runtime.

Built-in middleware

import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { secureHeaders } from 'hono/secure-headers';
import { compress } from 'hono/compress';
import { etag } from 'hono/etag';
import { jwt } from 'hono/jwt';

app.use(logger());
app.use(cors({ origin: 'https://app.example.com' }));
app.use(secureHeaders());
app.use(compress());
app.use(etag());

app.use('/api/*', jwt({ secret: 'secret' }));

→ 모든 production middleware.

Custom middleware

const auth = async (c, next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '');
  if (!token) return c.text('Unauthorized', 401);
  
  const user = await verifyToken(token);
  if (!user) return c.text('Invalid', 401);
  
  c.set('user', user);
  await next();
};

app.use('/api/*', auth);

app.get('/api/me', (c) => {
  const user = c.get('user');
  return c.json(user);
});

Type-safe context

type Variables = {
  user: User;
};

const app = new Hono<{ Variables: Variables }>();

app.use('*', async (c, next) => {
  c.set('user', { id: '1' });
  await next();
});

app.get('/', (c) => {
  const user = c.get('user');  // type: User
  return c.json(user);
});

Validator

import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

app.post(
  '/users',
  zValidator('json', z.object({ name: z.string(), email: z.string().email() })),
  (c) => {
    const data = c.req.valid('json');
    // type-safe
    return c.json({ id: '...', ...data });
  }
);

RPC mode (type-safe client)

// Server
const route = app.post('/users', zValidator('json', userSchema), (c) => {
  return c.json({ id: '...' });
});

export type AppType = typeof route;

// Client
import { hc } from 'hono/client';
import type { AppType } from './server';

const client = hc<AppType>('https://api.example.com');

const r = await client.users.$post({ json: { name: 'Alice', email: 'a@x' } });
const data = await r.json();   // type-safe

→ tRPC 식 type-safe RPC.

Streaming

import { stream, streamSSE } from 'hono/streaming';

app.get('/stream', (c) => {
  return stream(c, async (stream) => {
    for (let i = 0; i < 100; i++) {
      await stream.writeln(`chunk ${i}`);
      await stream.sleep(100);
    }
  });
});

app.get('/sse', (c) => {
  return streamSSE(c, async (stream) => {
    let id = 0;
    while (true) {
      await stream.writeSSE({ event: 'msg', data: 'hello', id: String(id++) });
      await stream.sleep(1000);
    }
  });
});

WebSocket (Bun / Deno)

import { upgradeWebSocket } from 'hono/cloudflare-workers';

app.get(
  '/ws',
  upgradeWebSocket((c) => ({
    onMessage: (event, ws) => ws.send(`echo: ${event.data}`),
    onClose: () => console.log('closed'),
  }))
);

Sub-app (modular)

const userApp = new Hono();
userApp.get('/', (c) => c.json([]));
userApp.get('/:id', (c) => c.json({ id: c.req.param('id') }));

const orderApp = new Hono();
orderApp.get('/', (c) => c.json([]));

const app = new Hono();
app.route('/users', userApp);
app.route('/orders', orderApp);

→ Module 별 분리.

Error handling

app.onError((err, c) => {
  console.error(err);
  return c.json({ error: err.message }, 500);
});

app.notFound((c) => c.text('Not found', 404));

File serving

import { serveStatic } from '@hono/node-server/serve-static';

app.use('/static/*', serveStatic({ root: './public' }));

OpenAPI integration

import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';

const app = new OpenAPIHono();

const route = createRoute({
  method: 'get',
  path: '/users/:id',
  request: { params: z.object({ id: z.string() }) },
  responses: {
    200: { content: { 'application/json': { schema: z.object({ id: z.string() }) } } },
  },
});

app.openapi(route, (c) => c.json({ id: c.req.valid('param').id }));

app.doc('/openapi.json', { openapi: '3.0.0', info: { title: 'API', version: '1.0' } });

→ Zod schema → OpenAPI auto.

Performance

Hono: ~30k req/sec on Bun.
Express: ~10k req/sec on Node.
Fastify: ~25k.

→ Hono 가 가장 빠름 + 작은.

Bundle size

Hono: ~12 KB.
Express: ~80 KB.
Fastify: ~90 KB.

→ Edge / serverless 친화.

Use case

- Cloudflare Worker.
- Vercel Edge.
- Bun server.
- Deno server.
- Node 서버 (alternative to Express).
- Lambda.

Production deploy

# Cloudflare
wrangler deploy

# Vercel
vercel --prod

# Bun
bun run server.ts

# Node
node server.js

vs Express

Express:
- 가장 mature.
- 큰 ecosystem.
- Node only.

Hono:
- Modern, edge-friendly.
- Type-safe.
- Multi-runtime.
- 작은 ecosystem (자라는).

→ New project = Hono.
Existing Express = stay.

vs Fastify

Fastify:
- Node-first.
- Plugin system.
- 빠름.

Hono:
- Edge-first.
- Web standards.
- 더 작은.

→ Edge / serverless = Hono.
Node-only enterprise = Fastify.

Middleware order

app.use(logger());           // 1st
app.use(cors());
app.use(jwt({ secret }));     // auth
app.use('/api/*', rateLimit); // rate
app.get('/api/users', ...);   // route

→ Order matters.

Context (c)

c.req.url             // string
c.req.method
c.req.header('...')
c.req.query('...')
c.req.param('...')
c.req.json()          // Promise<json>
c.req.formData()
c.req.text()

c.json({})            // Response
c.text('hi')
c.html('<p>hi</p>')
c.redirect('/path')
c.status(404)

c.set('key', val)
c.get('key')

c.env                 // Cloudflare Worker env

Auth patterns

// JWT
app.use('/api/*', jwt({ secret: c.env.JWT_SECRET }));

// Custom
const apiKey = c.req.header('X-API-Key');
if (apiKey !== c.env.API_KEY) return c.text('Unauthorized', 401);

// Basic auth
import { basicAuth } from 'hono/basic-auth';
app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' }));

Rate limit

const rateLimiter = new Map<string, { count: number; reset: number }>();

app.use('/api/*', async (c, next) => {
  const ip = c.req.header('x-real-ip') ?? 'unknown';
  const now = Date.now();
  const limit = rateLimiter.get(ip) ?? { count: 0, reset: now + 60000 };
  
  if (now > limit.reset) { limit.count = 0; limit.reset = now + 60000; }
  limit.count++;
  rateLimiter.set(ip, limit);
  
  if (limit.count > 100) return c.text('Rate limit', 429);
  await next();
});

→ Cloudflare = built-in. Self-host = manual.

Real-world

  • Cloudflare: Hono 의 큰 user.
  • Vercel: Edge functions.
  • Several startups: edge-first.

🤔 의사결정 기준

상황 추천
Edge / Cloudflare Hono
Node 새 project Hono
Existing Express Stay or migrate gradual
Type-safe RPC Hono RPC mode
OpenAPI Hono + zod-openapi
WebSocket Hono (Bun/Deno)

안티패턴

  • 모든 거 1 file: sub-app 사용.
  • No type-safe: zValidator.
  • No error handler: ugly.
  • Middleware order 잘못: auth before logger.
  • Big bundle: edge limit.

🤖 LLM 활용 힌트

  • Hono = modern Express.
  • Multi-runtime (CF / Node / Bun / Deno).
  • Type-safe RPC + OpenAPI.
  • 작은 bundle (edge 친화).

🔗 관련 문서