---
id: backend-hono-middleware-deep
title: Hono Middleware — modern Express alternative
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [backend, hono, vibe-coding]
tech_stack: { language: "TS", applicable_to: ["Backend"] }
applied_in: []
aliases: [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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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)
```ts
// 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
```ts
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)
```ts
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)
```ts
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
```ts
app.onError((err, c) => {
  console.error(err);
  return c.json({ error: err.message }, 500);
});

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

### File serving
```ts
import { serveStatic } from '@hono/node-server/serve-static';

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

### OpenAPI integration
```ts
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
```bash
# 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
```ts
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)
```ts
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
```ts
// 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
```ts
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 친화).

## 🔗 관련 문서
- [[Backend_Hono_Modern]]
- [[Backend_Edge_Runtime_Deep]]
- [[Backend_Edge_Functions]]
