Hono 완전 가이드 | Edge에 최적화된 초고속 웹 프레임워크
이 글의 핵심
Express를 대체하는 차세대 Edge 웹 프레임워크 Hono. Cloudflare Workers·Vercel·Deno·Bun 등 모든 런타임에서 작동하며, Express보다 10배 빠르고 TypeScript 타입 안전성을 제공합니다.
이 글의 핵심
Hono는 Express보다 10배 빠른 Edge 웹 프레임워크입니다. Cloudflare Workers·Vercel·Deno·Bun 등 모든 런타임에서 작동하며, TypeScript 타입 안전성과 미들웨어 시스템으로 생산성을 극대화합니다. 번들 크기는 12KB에 불과합니다.
목차
Hono란?
Hono(炎, 일본어로 “불꽃”)는 2022년 Yusuke Wada가 개발한 Edge 우선 웹 프레임워크입니다.
🚀 핵심 특징
1. 모든 런타임 지원
// 같은 코드가 모든 런타임에서 작동
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello!'));
// Cloudflare Workers
export default app;
// Deno
Deno.serve(app.fetch);
// Bun
export default { port: 3000, fetch: app.fetch };
// Node.js
serve(app);
2. Express보다 10배 빠름
벤치마크 (초당 요청):
Express: 25,000 req/s
Fastify: 65,000 req/s
Hono: 250,000 req/s
3. TypeScript 타입 안전성
// 라우트와 응답 타입이 자동 추론됨
app.get('/user/:id', (c) => {
const id = c.req.param('id'); // string으로 추론
return c.json({ id, name: 'Alice' });
});
4. 가벼움
Express: 210KB
Fastify: 450KB
Hono: 12KB
Hono 시작하기
Cloudflare Workers
# 프로젝트 생성
npm create hono@latest my-app
# 템플릿 선택: cloudflare-workers
cd my-app
npm install
npm run dev
// src/index.ts
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => {
return c.text('Hello Hono!');
});
export default app;
Node.js
npm install hono @hono/node-server
// index.ts
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Node.js!'));
serve({
fetch: app.fetch,
port: 3000,
});
console.log('Server running at http://localhost:3000');
Bun
bun install hono
// index.ts
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Bun!'));
export default {
port: 3000,
fetch: app.fetch,
};
라우팅
기본 라우트
import { Hono } from 'hono';
const app = new Hono();
// GET
app.get('/hello', (c) => c.text('GET /hello'));
// POST
app.post('/submit', (c) => c.text('POST /submit'));
// PUT
app.put('/update', (c) => c.text('PUT /update'));
// DELETE
app.delete('/remove', (c) => c.text('DELETE /remove'));
// 여러 메서드
app.on(['GET', 'POST'], '/multi', (c) => {
return c.text(`Method: ${c.req.method}`);
});
// 모든 메서드
app.all('/any', (c) => c.text('Any method'));
export default app;
경로 매개변수
// /user/:id
app.get('/user/:id', (c) => {
const id = c.req.param('id');
return c.json({ userId: id });
});
// /post/:id/comment/:commentId
app.get('/post/:id/comment/:commentId', (c) => {
const { id, commentId } = c.req.param();
return c.json({ postId: id, commentId });
});
// 선택적 매개변수
app.get('/user/:id?', (c) => {
const id = c.req.param('id') || 'default';
return c.text(`User: ${id}`);
});
// 와일드카드
app.get('/files/*', (c) => {
return c.text(`Path: ${c.req.path}`);
});
쿼리 파라미터
// /search?q=hello&page=2
app.get('/search', (c) => {
const q = c.req.query('q'); // 'hello'
const page = c.req.query('page'); // '2'
// 또는 한 번에
const { q, page } = c.req.query();
return c.json({ query: q, page });
});
요청/응답
JSON 요청 처리
app.post('/user', async (c) => {
const body = await c.req.json();
return c.json({
message: 'User created',
user: body,
}, 201);
});
// FormData
app.post('/upload', async (c) => {
const formData = await c.req.formData();
const file = formData.get('file');
return c.json({ filename: file?.name });
});
// Text
app.post('/text', async (c) => {
const text = await c.req.text();
return c.text(`Received: ${text}`);
});
응답 타입
// JSON
app.get('/json', (c) => c.json({ message: 'Hello' }));
// Text
app.get('/text', (c) => c.text('Hello'));
// HTML
app.get('/html', (c) => c.html('<h1>Hello</h1>'));
// Redirect
app.get('/redirect', (c) => c.redirect('/new-path'));
// 커스텀 응답
app.get('/custom', (c) => {
return new Response('Custom', {
status: 200,
headers: { 'X-Custom': 'Header' },
});
});
미들웨어
내장 미들웨어
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt';
import { compress } from 'hono/compress';
const app = new Hono();
// 로깅
app.use('*', logger());
// CORS
app.use('*', cors({
origin: 'https://example.com',
credentials: true,
}));
// 압축
app.use('*', compress());
// JWT 인증
app.use('/api/*', jwt({
secret: 'my-secret-key',
}));
app.get('/api/protected', (c) => {
const payload = c.get('jwtPayload');
return c.json({ user: payload });
});
export default app;
커스텀 미들웨어
// 실행 시간 측정
const timing = () => {
return async (c, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
c.res.headers.set('X-Response-Time', `${ms}ms`);
};
};
app.use('*', timing());
// 인증 미들웨어
const auth = () => {
return async (c, next) => {
const token = c.req.header('Authorization');
if (!token) {
return c.json({ error: 'Unauthorized' }, 401);
}
// 토큰 검증...
c.set('userId', 123);
await next();
};
};
app.use('/api/*', auth());
실전 프로젝트: REST API
// src/index.ts
import { Hono } from 'hono';
import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';
const app = new Hono();
// In-memory DB (실제로는 D1 사용)
const users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' },
];
// GET /users
app.get('/users', (c) => {
return c.json(users);
});
// GET /users/:id
app.get('/users/:id', (c) => {
const id = parseInt(c.req.param('id'));
const user = users.find(u => u.id === id);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(user);
});
// POST /users (Zod 검증)
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
app.post('/users', zValidator('json', createUserSchema), (c) => {
const body = c.req.valid('json');
const newUser = {
id: users.length + 1,
...body,
};
users.push(newUser);
return c.json(newUser, 201);
});
// PATCH /users/:id
app.patch('/users/:id', async (c) => {
const id = parseInt(c.req.param('id'));
const body = await c.req.json();
const user = users.find(u => u.id === id);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
Object.assign(user, body);
return c.json(user);
});
// DELETE /users/:id
app.delete('/users/:id', (c) => {
const id = parseInt(c.req.param('id'));
const index = users.findIndex(u => u.id === id);
if (index === -1) {
return c.json({ error: 'User not found' }, 404);
}
users.splice(index, 1);
return c.json({ success: true });
});
export default app;
중첩 라우팅
// users.ts
import { Hono } from 'hono';
const usersApp = new Hono();
usersApp.get('/', (c) => c.json({ users: [] }));
usersApp.get('/:id', (c) => c.json({ id: c.req.param('id') }));
usersApp.post('/', (c) => c.json({ created: true }));
// posts.ts
const postsApp = new Hono();
postsApp.get('/', (c) => c.json({ posts: [] }));
postsApp.get('/:id', (c) => c.json({ id: c.req.param('id') }));
// index.ts
const app = new Hono();
app.route('/users', usersApp);
app.route('/posts', postsApp);
export default app;
Hono vs Express vs Fastify
| 기능 | Hono | Express | Fastify |
|---|---|---|---|
| 속도 | ⚡⚡⚡ 초고속 | ⚡ 보통 | ⚡⚡ 빠름 |
| Edge 지원 | ✅ 완벽 | ❌ 불가 | ❌ 불가 |
| TypeScript | ✅ 기본 | ⚠️ 수동 | ✅ 기본 |
| 번들 크기 | 12KB | 210KB | 450KB |
| 미들웨어 | ✅ | ✅ | ✅ |
| 생태계 | 🌱 새로움 | 🌳 성숙 | 🌿 성장 중 |
핵심 정리
✅ Hono의 장점
- 압도적인 속도: Express보다 10배 빠름
- Edge 최적화: Cloudflare Workers·Vercel Edge
- 멀티 런타임: Deno·Bun·Node.js 모두 지원
- TypeScript 우선: 완벽한 타입 안전성
- 가벼움: 12KB 번들 크기
🚀 다음 단계
- Hono 공식 문서에서 심화 학습
- Hono GitHub에서 소스 코드 탐색
- Discord에서 커뮤니티 참여
시작하기:
npm create hono@latest로 5분 만에 프로젝트를 시작하고, Express보다 10배 빠른 API 서버를 만드세요! 🚀