Files
2nd/10_Wiki/Topics/Coding/Backend_Hono_Modern.md
T
2026-05-09 22:47:42 +09:00

7.8 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-modern Hono / Elysia / Modern TS Frameworks Coding draft B conceptual 2026-05-09 2026-05-09
backend
hono
elysia
vibe-coding
language applicable_to
TS / Bun / Node
Backend
Hono
Elysia
Bun.serve
Express alternative
Web Standard
modern TS framework

Hono / Elysia

Express 의 modern 후속. Web Standard (Request/Response) 기반 + edge runtime 호환 + type-safe. Hono = universal, Elysia = Bun 친화.

📖 핵심 개념

  • Web Standard: Request / Response (browser native).
  • Edge: Cloudflare Workers / Deno / Bun.
  • Type-safe: handler 의 query / body / response 자동 inferred.
  • 작은 + 빠름.

💻 코드 패턴

Hono 기본

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') }));

app.post('/users', async (c) => {
  const body = await c.req.json();
  return c.json({ id: '...', ...body }, 201);
});

export default app;

Bun 으로 실행

bun run --hot src/index.ts

Cloudflare Workers

// wrangler.toml
[observability]
enabled = true

// src/index.ts
import { Hono } from 'hono';

const app = new Hono<{ Bindings: { DB: D1Database; CACHE: KVNamespace } }>();

app.get('/users/:id', async (c) => {
  const id = c.req.param('id');
  const cached = await c.env.CACHE.get(`user:${id}`);
  if (cached) return c.json(JSON.parse(cached));
  
  const user = await c.env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(id).first();
  await c.env.CACHE.put(`user:${id}`, JSON.stringify(user), { expirationTtl: 60 });
  return c.json(user);
});

export default app;

Vercel Edge / Deno

import { Hono } from 'hono';
const app = new Hono();
// ... routes
export default app;  // 자동 detect

Middleware

import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { compress } from 'hono/compress';
import { jwt } from 'hono/jwt';

app.use('*', logger());
app.use('*', cors({ origin: 'https://app.com' }));
app.use('*', compress());

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

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

Zod validator

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

const CreateUser = z.object({
  email: z.string().email(),
  name: z.string().min(1),
});

app.post('/users', zValidator('json', CreateUser), async (c) => {
  const data = c.req.valid('json');  // typed
  // ...
});

Hono RPC (TS client)

// server
const route = app.get('/users/:id', (c) => c.json({ id: c.req.param('id'), name: 'A' }));
export type AppType = typeof route;

// client (frontend)
import { hc } from 'hono/client';
import type { AppType } from '../server';

const client = hc<AppType>('http://localhost:3000');

const r = await client.users[':id'].$get({ param: { id: '1' } });
const data = await r.json();  // typed!

→ tRPC 비슷 — type 가 client / server 공유.

Streaming (SSE)

import { streamSSE } from 'hono/streaming';

app.get('/stream', (c) => {
  return streamSSE(c, async (stream) => {
    while (true) {
      await stream.writeSSE({
        data: JSON.stringify({ time: Date.now() }),
        event: 'tick',
        id: crypto.randomUUID(),
      });
      await stream.sleep(1000);
    }
  });
});

Error handler

import { HTTPException } from 'hono/http-exception';

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status);
  }
  return c.json({ error: 'Internal' }, 500);
});

app.get('/protected', (c) => {
  const auth = c.req.header('Authorization');
  if (!auth) throw new HTTPException(401, { message: 'Unauthorized' });
  return c.json({ ok: true });
});

Elysia (Bun-only, 매우 빠름)

import { Elysia, t } from 'elysia';

const app = new Elysia()
  .get('/', () => 'Hello')
  .get('/users/:id', ({ params: { id } }) => ({ id }))
  .post('/users', ({ body }) => ({ ...body, id: '...' }), {
    body: t.Object({
      email: t.String({ format: 'email' }),
      name: t.String({ minLength: 1 }),
    }),
  })
  .listen(3000);

console.log(`http://localhost:${app.server?.port}`);

→ TypeBox (JSON Schema) — Bun native.

Elysia plugins

import { swagger } from '@elysiajs/swagger';
import { jwt } from '@elysiajs/jwt';
import { cors } from '@elysiajs/cors';

app
  .use(swagger())  // /swagger 자동 docs
  .use(cors())
  .use(jwt({ secret: process.env.JWT_SECRET }));

Bun.serve (raw, 가장 빠름)

Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === '/') return new Response('Hello');
    if (url.pathname.startsWith('/api/')) return apiHandler(req);
    return new Response('Not found', { status: 404 });
  },
});

→ Framework 없이. 가장 raw.

Performance

Bun.serve:    > 100K req/s (single core)
Elysia:       ~80K req/s
Hono on Bun:  ~80K req/s
Hono on Node: ~30K req/s
Express:      ~10K req/s

→ 측정 + workload 따라.

File-based routing

Hono = code-first.
Elysia = code-first.

File-based 원하면:
- Next.js App Router
- Tanstack Start
- Astro endpoints
- Hono + 자체 file scanner

Database 통합

// Hono + Drizzle
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

const sql = postgres(process.env.DATABASE_URL!);
const db = drizzle(sql);

app.get('/users/:id', async (c) => {
  const user = await db.select().from(usersTable).where(eq(usersTable.id, c.req.param('id'))).limit(1);
  return c.json(user[0]);
});

Edge DB combo

Cloudflare Workers + D1: Hono + D1 binding
Vercel Edge + Neon: Hono + neon HTTP
Bun + Postgres: Hono / Elysia + Bun pg

vs Express

Express:
+ 커뮤니티 큼
+ 미들웨어 많음
- 옛 callback API
- Type 약함
- Edge 호환 X (Node only)

Hono / Elysia:
+ Modern TS
+ Edge runtime
+ 빠름
+ Type-safe
- 작은 ecosystem (커지는 중)

Migration (Express → Hono)

// Express
app.get('/users/:id', async (req, res) => {
  res.json(await getUser(req.params.id));
});

// Hono
app.get('/users/:id', async (c) => {
  return c.json(await getUser(c.req.param('id')));
});

→ 비슷. Migration 가능.

Testing (Hono)

import { Hono } from 'hono';
import { test, expect } from 'vitest';

const app = new Hono();
app.get('/', (c) => c.text('Hello'));

test('GET /', async () => {
  const res = await app.request('/');
  expect(res.status).toBe(200);
  expect(await res.text()).toBe('Hello');
});

→ App.request — 외부 server 필요 X.

Deployment options

Hono:
- Cloudflare Workers
- Vercel Edge
- AWS Lambda (with adapter)
- Bun
- Node
- Deno

Elysia:
- Bun (only)

Build / size

Hono: ~12 KB (gzip)
Elysia: ~30 KB
Express: ~120 KB

→ Edge runtime 친화.

🤔 의사결정 기준

환경 추천
Edge runtime Hono
Bun max performance Elysia
Node + 큰 ecosystem Hono 또는 Express
Multi-cloud / portable Hono
File-based + full-stack Next / Tanstack Start
Raw / 가장 빠른 Bun.serve

안티패턴

  • Express + Edge runtime: 호환 X.
  • Node-specific module on Edge: 깨짐.
  • fetch / Response 의 standard 알기 X: API confusion.
  • Hono RPC + 큰 schema: 빌드 시간.
  • Elysia + Node: Bun 만.
  • Middleware 너무 많이: latency.
  • Type 안 활용: 의미 없는 framework 선택.

🤖 LLM 활용 힌트

  • Hono = universal modern.
  • Elysia = Bun 친화 + 빠름.
  • Web Standard API (Request / Response).
  • Hono RPC = type-safe TS fullstack.

🔗 관련 문서