1204 字
6 分钟
Hello, Hono
2025-05-24

为什么选择 Hono.js#

Hono 起初是为 Cloudflare Workers 设计的超轻量 Web 框架,现在已支持 Deno、Bun、Node.js、Vercel Edge、Lagon 等多种运行时。它兼具以下特性:

  • 快速:核心仅 ~15KB,响应链路短,天然适合边缘侧(Edge)与无服务器函数。
  • 兼容 Fetch 标准:上下文就是 Request/Response,易于在不同运行时迁移。
  • TypeScript 友好:严格的类型推导,加上 hc/zod 等生态可自动生成客户端与 OpenAPI。
  • 中间件生态:内置 CORS、Basic Auth、JWT、Validator 等工具,也能继承 Express 风格中间件。

本文整理 Hono 在实际项目中的最佳实践,帮助你搭建稳定、可维护的微服务 API。

项目脚手架#

推荐使用官方 CLI:

npm create hono@latest my-hono-app
cd my-hono-app
npm install
npm run dev

生成的项目默认使用 TypeScript 与 ESLint,可根据运行时切换 adapter

  • cloudflare-workers
  • bun
  • deno
  • node-server(Node.js 运行,配合 Fastly Compute、Lambda 等)

配置建议:

  • tsconfig.json 开启 strict, noUncheckedIndexedAccess
  • 配置 eslint-config-honotypescript-eslint 保证一致性。
  • 使用 @hono/zod-validator 做输入校验。

路由与模块化#

Hono 按照路径注册 handler,建议按业务拆分:

import { Hono } from 'hono';
import { userRouter } from './routes/users';
import { authRouter } from './routes/auth';

const app = new Hono();

app.route('/auth', authRouter);
app.route('/users', userRouter);

export default app;

routes/users.ts

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

export const userRouter = new Hono()
  .get(
    '/',
    zValidator('query', z.object({ page: z.coerce.number().int().min(1).default(1) })),
    async (c) => {
      const { page } = c.req.valid('query');
      const result = await c.get('services').user.list({ page });
      return c.json(result);
    }
  )
  .post(
    '/',
    zValidator('json', z.object({ email: z.string().email(), password: z.string().min(8) })),
    async (c) => {
      const payload = c.req.valid('json');
      const user = await c.get('services').user.create(payload);
      return c.json(user, 201);
    }
  );

要点:

  • 尽量在路由文件中保持“瘦控制器”,把业务逻辑下沉到 service/domain 层。
  • 通过 c.get()/c.set() 将服务或依赖注入 Context
  • 利用 zod 校验 + 类型推导避免重复声明类型。

中间件与上下文#

常用中间件示例:

import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { poweredBy } from 'hono/powered-by';
import { secureHeaders } from 'hono/secure-headers';

app.use('*', poweredBy());
app.use('*', logger());
app.use('*', secureHeaders());
app.use(
  '/api/*',
  cors({
    origin: ['https://app.example.com'],
    allowMethods: ['GET', 'POST', 'PATCH', 'DELETE'],
    allowHeaders: ['Authorization', 'Content-Type'],
  })
);

依赖注入:

import type { Services } from './services';

type Bindings = {
  SERVICES: Services;
};

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

app.use('*', async (c, next) => {
  c.set('services', c.env.SERVICES);
  await next();
});

执行顺序遵循注册顺序,注意在 Edge 环境中避免阻塞型操作(例如 crypto.randomUUID 可替代 uuid 包)。

错误处理#

统一错误处理能提升 DX:

app.onError((err, c) => {
  c.get('logger')?.error(err);

  if (err instanceof HTTPException) {
    return err.getResponse();
  }

  return c.json(
    {
      code: 'INTERNAL_ERROR',
      message: '服务器开小差了',
    },
    500
  );
});

app.notFound((c) =>
  c.json(
    {
      code: 'NOT_FOUND',
      message: `Route ${c.req.method} ${c.req.path} 不存在`,
    },
    404
  )
);
  • 使用 HTTPException 抛出领域错误,例如 throw new HTTPException(403, { message: 'Forbidden' })
  • 在日志中记录 requestId(可从 header 或 crypto.randomUUID() 生成)。
  • Edge 平台通常有 ExecutionContext.waitUntil,可以结合 app.use 注入日志/埋点。

数据访问与性能#

  • Cloudflare D1、PlanetScale、Supabase 可通过 Context.env 注入客户端。
  • 在 Edge 环境使用 fetch 调用内部服务时,注意复用 Request 对象或使用 Cache API
  • 结合 itty-router-openapihono-openapi 自动生成文档。
  • 当需要 WebSocket/SSE,可引用 hono/wshono/sse

缓存策略:

app.get('/health', async (c) => {
  return c.text('ok', 200, {
    'Cache-Control': 'max-age=30',
  });
});

app.get('/articles', cacheMiddleware(), async (c) => {
  // ...
});

在 Cloudflare Workers 中可用 c.executionCtx.waitUntil(cache.put(...)) 延迟写缓存。

安全最佳实践#

  • 使用 secureHeaders() 设置 Content-Security-Policy, Strict-Transport-Security, X-Frame-Options

  • JWT 验证使用 hono/jwt

    app.use(
      '/api/*',
      jwt({
        secret: c.env.JWT_SECRET,
        cookie: 'access_token',
        algorithm: 'HS256',
      })
    );
    
  • 对外部输入全部走 zod/valibot 校验,防止类型穿透。

  • 对于文件上传,结合 R2/S3 签名上传 + 临时 URL。

  • 记录审计日志:可将请求信息写入 Workers Analytics Engine 或第三方服务。

测试策略#

单元测试#

import { describe, it, expect } from 'vitest';
import app from '../src/app';

describe('GET /health', () => {
  it('should return ok', async () => {
    const res = await app.request('/health');
    expect(res.status).toBe(200);
    expect(await res.text()).toBe('ok');
  });
});

Hono 提供 app.request 直接在 Node 里模拟请求,无需启动服务器。

集成测试#

  • Cloudflare Workers:使用 @cloudflare/workers-typesminiflare
  • Node 环境:搭配 supertest
  • Edge Function:在部署前使用平台提供的本地模拟器(如 wrangler devvercel dev)。

部署与观察#

  • Cloudflare Workerswrangler deploy;配置 Durable Objects、KV、R2 于 wrangler.toml
  • Vercel Edge:导出 export default app,在 vercel.json 指定 runtime: "edge"
  • Node Server:使用 @hono/node-serverfastly-hono-adapter
  • Docker:将 app.fetch 包装在 server.listen 中,注意 Node 端口。

监控与日志:

  • Cloudflare Logs / Workers Analytics Engine。
  • OpenTelemetry:可结合 hono/opentelemetry 输出 trace。
  • 使用平台原生指标(Vercel Speed Insights、Deno Deploy Metrics)。

常见陷阱#

  • Edge 环境没有 Node polyfills,避免依赖 fs, crypto.randomBytes 等 Node 特有 API。
  • await 中不要阻塞事件循环:使用 Promise.all 并行执行。
  • 注意 Request/Response 流只能读取一次;需要复用时调用 req.clone().
  • app.fire() 必须在 fetch handler 中调用,在 Cloudflare 中通常是 export default app.
  • 如果在中间件中抛异常,却返回 200,多半是 await next() 忘写。

推荐资源#

合理运用以上实践,可以让 Hono 成为构建高性能、易维护 API 的利器,无论是 SaaS 后端、BFF 还是边缘函数都能胜任。

Hello, Hono
https://bangwu.top/posts/honojs/
作者
棒无
发布于
2025-05-24
许可协议
CC BY-NC-SA 4.0