[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user