跳至主要內容

Hono:專為邊緣運算設計的極輕量 Web 框架

6 分鐘閱讀 1,000 字

Hono:專為邊緣運算設計的極輕量 Web 框架

在 Node.js 的 Web 框架世界,Express 統治了十多年。但隨著 Cloudflare Workers、Deno Deploy、Bun 等新執行環境的崛起,需要一個能在多個執行環境中運行、體積極小的框架。Hono(日文「炎」)應運而生。

Hono 的核心特點

  • 極輕量:核心不到 14KB(壓縮後),零依賴
  • 多執行環境:同一份程式碼可在 Cloudflare Workers、Node.js、Deno、Bun、AWS Lambda 運行
  • TypeScript 優先:型別推斷極為完善
  • 速度快:基準測試中通常是最快的 JS Web 框架之一
  • 類 Express API:學習曲線低

安裝與基本設定

# 使用官方 CLI 建立專案
npm create hono@latest my-app
# 選擇執行環境(cloudflare-workers / nodejs / deno / bun)

# 或手動安裝
npm install hono

Hello World

import { Hono } from 'hono';

const app = new Hono();

app.get('/', (c) => {
  return c.text('Hello, Hono!');
});

app.get('/json', (c) => {
  return c.json({ message: 'Hello', timestamp: Date.now() });
});

export default app;

在不同環境中啟動:

# Node.js
npm install @hono/node-server
import { serve } from '@hono/node-server';
import app from './app';

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.log('Server running on http://localhost:3000');
});

路由系統

import { Hono } from 'hono';

const app = new Hono();

// 基本路由
app.get('/users', listUsers);
app.post('/users', createUser);
app.get('/users/:id', getUser);
app.put('/users/:id', updateUser);
app.delete('/users/:id', deleteUser);

// 路由參數
app.get('/posts/:year/:month', (c) => {
  const { year, month } = c.req.param();
  return c.json({ year, month });
});

// 查詢字串
app.get('/search', (c) => {
  const query = c.req.query('q');
  const page = Number(c.req.query('page') ?? 1);
  return c.json({ query, page });
});

// 路由群組
const api = new Hono().basePath('/api');
api.get('/health', (c) => c.json({ status: 'ok' }));

app.route('/', api);

中介軟體(Middleware)

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { jwt } from 'hono/jwt';
import { prettyJSON } from 'hono/pretty-json';

const app = new Hono();

// 內建中介軟體
app.use('*', logger());
app.use('*', cors({
  origin: ['https://myapp.com', 'http://localhost:5173'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE']
}));
app.use('/api/*', prettyJSON());

// JWT 認證
app.use('/protected/*', jwt({ secret: process.env.JWT_SECRET! }));

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

// 自訂中介軟體
app.use('*', async (c, next) => {
  const start = Date.now();
  await next();
  const duration = Date.now() - start;
  c.res.headers.set('X-Response-Time', `${duration}ms`);
});

型別安全的路由

Hono 最強的特性之一:完整的型別推斷,包括路由參數、查詢字串、請求體和回應:

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

const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150)
});

const app = new Hono();

app.post(
  '/users',
  zValidator('json', CreateUserSchema),
  async (c) => {
    // body 已完全型別化
    const body = c.req.valid('json');
    // body.name: string
    // body.email: string
    // body.age: number

    const user = await db.createUser(body);
    return c.json(user, 201);
  }
);

RPC 模式:型別安全的 API 呼叫

Hono 獨特的 RPC 功能,讓前端呼叫後端 API 時享有完整的型別支援:

// server.ts
const route = app
  .get('/todos', async (c) => {
    const todos = await getTodos();
    return c.json({ todos });
  })
  .post('/todos',
    zValidator('json', z.object({ title: z.string() })),
    async (c) => {
      const { title } = c.req.valid('json');
      const todo = await createTodo(title);
      return c.json(todo, 201);
    }
  );

export type AppType = typeof route;

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

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

// 完全型別安全的 API 呼叫
const res = await client.todos.$get();
const { todos } = await res.json();
// todos 的型別從伺服器端自動推斷

await client.todos.$post({
  json: { title: '學習 Hono' }
});

在 Cloudflare Workers 部署

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

type Bindings = {
  DB: D1Database;        // Cloudflare D1
  KV: KVNamespace;       // Cloudflare KV
  BUCKET: R2Bucket;      // Cloudflare R2
  JWT_SECRET: string;    // 環境變數
};

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

app.get('/kv/:key', async (c) => {
  const key = c.req.param('key');
  const value = await c.env.KV.get(key);

  if (!value) return c.notFound();
  return c.text(value);
});

app.get('/db/users', async (c) => {
  const { results } = await c.env.DB.prepare('SELECT * FROM users').all();
  return c.json(results);
});

export default app;
# 部署到 Cloudflare Workers
npx wrangler deploy

錯誤處理

import { Hono, HTTPException } from 'hono';

const app = new Hono();

// 自訂錯誤處理
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status);
  }

  console.error('Unexpected error:', err);
  return c.json({ error: 'Internal Server Error' }, 500);
});

// 404 處理
app.notFound((c) => {
  return c.json({ error: `路由 ${c.req.path} 不存在` }, 404);
});

// 在路由中拋出 HTTP 異常
app.get('/users/:id', async (c) => {
  const user = await findUser(c.req.param('id'));
  if (!user) {
    throw new HTTPException(404, { message: '使用者不存在' });
  }
  return c.json(user);
});

與 Express 的比較

特性 Hono Express
體積 ~14KB ~200KB+
TypeScript 原生 需 @types
多執行環境 Node.js only
型別安全路由
RPC 客戶端
生態系 成長中 非常豐富

小結

Hono 不是要取代 Express,而是為新的執行環境生態而生。如果你在開發 Cloudflare Workers 應用、需要在多個執行環境部署同一份程式碼、或者重視 TypeScript 的型別安全,Hono 是目前最佳的選擇。它的 RPC 模式更是讓全端開發者眼前一亮——前後端型別共用,告別手動維護 API 型別定義的痛苦。

分享這篇文章