mcpField
H
hono

hono

github.com/dralgorhythm开发12954 次安装14 周前更新
代码仓库

关于 hono

这项技能指导使用 Hono 构建 API 和服务端应用,涵盖路由、中间件、验证、类型安全与测试,并介绍多个运行时的设置。

主要功能

  1. 01跨运行时设置 Hono
  2. 02HTTP 路由与路由分组
  3. 03中间件与错误处理
  4. 04使用 Zod 验证请求
  5. 05类型化上下文与 RPC
  6. 06进程内 API 测试

适用场景

  1. 01搭建 Hono API
  2. 02使用 Zod 验证路由输入
  3. 03组织不同版本的 API 路由
  4. 04无需启动服务端测试端点

MCP 相关

该技能建议通过 Context7 MCP 先调用 `resolve-library-id`,再调用 `query-docs`,获取 Hono API 参考和运行时设置示例。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dralgorhythm/claude-agentic-framework --skill hono

安装后在 Claude Code 中输入/hono确认已加载。

详细介绍

Hono

如需完整的 API 参考、所有内置中间件以及更多运行时设置示例,请使用 Context7 MCP(resolve-library-id,然后使用 query-docs)。

概览

轻量、快速的 Web 框架,适用于 API 和服务端应用。Hono 4.x 可通过统一的 API 在 Node.js、Bun、Deno 和 Cloudflare Workers 上运行。

安装:pnpm add hono

工作流

创建基础 API:

  1. 创建 Hono 应用实例:const app = new Hono()
  2. 使用 HTTP 方法定义路由
  3. 添加中间件(CORS、日志记录、错误处理)
  4. 导出应用以供运行时适配器使用
  5. 使用 curl 或 Postman 测试端点

添加验证:

  1. 安装 Zod:pnpm add zod @hono/zod-validator
  2. 使用 Zod 定义架构
  3. 在路由中应用 zValidator 中间件
  4. 处理验证错误
  5. 通过 c.req.valid() 访问类型安全的请求数据

运行时设置

// Node.js
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
serve({ fetch: app.fetch, port: 3000 });

// Bun
export default { port: 3000, fetch: app.fetch };

// Cloudflare Workers
export default app;

路由

// HTTP methods
app.get('/users', (c) => c.json({ users: [] }));
app.post('/users', (c) => c.json({ created: true }, 201));
app.put('/users/:id', (c) => c.json({ updated: true }));
app.delete('/users/:id', (c) => c.json({ deleted: true }));
app.on(['GET', 'POST'], '/multi', (c) => c.text(c.req.method));

// Path parameters
app.get('/users/:id', (c) => {
  const id = c.req.param('id');
  return c.json({ userId: id });
});

// Multiple params
app.get('/posts/:postId/comments/:commentId', (c) => {
  const { postId, commentId } = c.req.param();
  return c.json({ postId, commentId });
});

// Wildcard
app.get('/files/*', (c) => c.text('File handler'));

// Regex constraint (only numeric IDs)
app.get('/posts/:id{[0-9]+}', (c) => c.json({ id: c.req.param('id') }));
路由组
const v1 = new Hono();
v1.get('/users', (c) => c.json({ version: 1, users: [] }));

const v2 = new Hono();
v2.get('/users', (c) => c.json({ version: 2, users: [] }));

app.route('/api/v1', v1);
app.route('/api/v2', v2);

请求处理

// Query params
app.get('/search', (c) => {
  const q = c.req.query('q');
  const page = c.req.query('page') ?? '1';
  return c.json({ q, page });
});

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

// Headers
app.get('/me', (c) => {
  const auth = c.req.header('Authorization');
  c.header('X-Custom-Header', 'value');
  return c.json({ auth });
});

响应类型

c.json({ data: 'value' })          // JSON (default 200)
c.json({ created: true }, 201)     // JSON with status
c.text('OK')                       // Plain text
c.html('<h1>Hello</h1>')           // HTML
c.redirect('/new')                 // 302 redirect
c.redirect('https://example.com', 301)
c.notFound()                       // 404

中间件

import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { secureHeaders } from 'hono/secure-headers';
import { prettyJSON } from 'hono/pretty-json';

app.use('*', logger());
app.use('*', secureHeaders());
app.use('*', cors({
  origin: ['http://localhost:3000', 'https://example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
}));
if (process.env.NODE_ENV === 'development') {
  app.use('*', prettyJSON());
}
自定义中间件
// Auth middleware — store data in context
const authMiddleware = async (c, next) => {
  const token = c.req.header('Authorization');
  if (!token) return c.json({ error: 'Unauthorized' }, 401);
  c.set('user', { id: 1, name: 'Alice' }); // stored in context
  await next();
};

app.use('/api/*', authMiddleware);

app.get('/api/profile', (c) => {
  const user = c.get('user');
  return c.json({ user });
});

使用 Zod 进行验证

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

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

// Validate body
app.post('/users', zValidator('json', userSchema), async (c) => {
  const user = c.req.valid('json'); // fully typed
  return c.json({ created: true, user }, 201);
});

// Validate path params
app.get('/users/:id', zValidator('param', z.object({ id: z.string().regex(/^\d+$/) })), (c) => {
  const { id } = c.req.valid('param');
  return c.json({ userId: id });
});

// Custom validation error response
app.post('/users', zValidator('json', userSchema, (result, c) => {
  if (!result.success) {
    return c.json({ error: 'Validation failed', details: result.error.flatten() }, 400);
  }
}), handler);

错误处理

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

// Global error handler
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message, status: err.status }, err.status);
  }
  return c.json({
    error: 'Internal Server Error',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined,
  }, 500);
});

// 404 handler
app.notFound((c) => c.json({ error: 'Not Found', path: c.req.path }, 404));

// Throw HTTP exceptions from route handlers
app.get('/protected', (c) => {
  throw new HTTPException(403, { message: 'Forbidden' });
});

类型安全

类型化上下文变量
type Env = {
  Variables: { user: { id: number; name: string } };
};

const app = new Hono<Env>();

app.use('/api/*', async (c, next) => {
  c.set('user', { id: 1, name: 'Alice' }); // type-checked
  await next();
});

app.get('/api/profile', (c) => {
  const user = c.get('user'); // fully typed
  return c.json({ user });
});
RPC 类型安全(Hono 客户端)
// server.ts — export the app type
const app = new Hono()
  .get('/posts', (c) => c.json({ posts: [] }))
  .post('/posts', async (c) => c.json({ created: true }, 201));

export type AppType = typeof app;

// client.ts — fully typed calls, no separate OpenAPI spec needed
import { hc } from 'hono/client';
import type { AppType } from './server';

const client = hc<AppType>('http://localhost:3000');
const res = await client.posts.$get();
const data = await res.json(); // { posts: [] }

测试

import { describe, it, expect } from 'vitest';

describe('API', () => {
  const app = new Hono();
  app.get('/hello', (c) => c.json({ message: 'Hello' }));

  it('returns hello', async () => {
    const res = await app.request('/hello');
    expect(res.status).toBe(200);
    expect(await res.json()).toEqual({ message: 'Hello' });
  });

  it('handles POST', async () => {
    app.post('/users', async (c) => {
      const body = await c.req.json();
      return c.json({ created: true, user: body }, 201);
    });

    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: 'Alice' }),
    });

    expect(res.status).toBe(201);
  });
});

最佳实践

  • 使用路由组将相关端点组织到模块化路由器中
  • 使用 Zod 验证所有输入,确保类型安全并进行运行时验证
  • 谨慎应用中间件——仅在每个路由组中使用所需的中间件
  • 设置明确的 CORS 策略以用于生产环境——切勿在生产环境中使用宽松的 CORS
  • 对中间件中设置的变量使用类型化上下文(Hono<Env>)
  • 使用 app.onError() 全局处理错误,以确保错误响应一致
  • 使用 HTTPException,而不是手动构造错误响应
  • 使用 app.request() 进行测试——Hono 内置的测试工具(无需服务器)
  • 利用 RPC 类型实现类型安全的客户端-服务端通信

反模式

  • ❌ 在路由之后应用日志记录中间件(这些路由不会被记录)
  • ❌ 忘记在中间件中 await next()(会中断中间件链)
  • ❌ 仅在特定路由上使用 cors()(预检请求需要全局 CORS)
  • ❌ 多次解析请求正文(首次解析后应缓存)
  • ❌ 不验证路径参数(始终验证用户输入)
  • ❌ 使用 any 类型,而不是适当的 Hono 泛型
  • ❌ 在 CORS 配置中硬编码来源(请使用环境变量)
  • ❌ 缺少错误处理器(会导致未处理的 Promise 拒绝)
  • ❌ 忘记为运行时适配器导出应用

反馈循环

测试端点:

curl -X GET http://localhost:3000/api/users
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"[email protected]"}'

验证测试:

# Should return 400 with validation details
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"","email":"invalid"}'

性能测试:

pnpm add -D autocannon
npx autocannon -c 100 -d 10 http://localhost:3000/api/users
# Target: <10ms p99 latency for simple endpoints

评论

支持 Markdown