hono
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHono
Hono
Overview
概述
Lightweight, fast web framework for building APIs and server-side applications. Hono 4.x works across Node.js, Bun, Deno, Cloudflare Workers, and other runtimes with a consistent API.
Version: 4.x
Install:
pnpm add honoHono是一款轻量、高性能的Web框架,用于构建API和服务端应用。Hono 4.x拥有统一的API,可在Node.js、Bun、Deno、Cloudflare Workers等多种运行时环境中运行。
版本:4.x
安装:
pnpm add honoWorkflows
工作流程
Creating a basic API:
- Create Hono app instance:
const app = new Hono() - Define routes with HTTP methods
- Add middleware (CORS, logger, error handling)
- Export app for runtime adapter
- Test endpoints with curl or Postman
Adding validation:
- Install Zod:
pnpm add zod @hono/zod-validator - Define schemas with Zod
- Apply validator middleware to routes
- Handle validation errors
- Type-safe request access
创建基础API:
- 创建Hono应用实例:
const app = new Hono() - 基于HTTP方法定义路由
- 添加中间件(CORS、日志、错误处理)
- 导出应用以适配对应运行时
- 使用curl或Postman测试接口
添加数据校验:
- 安装Zod:
pnpm add zod @hono/zod-validator - 使用Zod定义校验规则
- 为路由应用校验中间件
- 处理校验错误
- 类型安全的请求访问
Basic Setup
基础配置
Minimal Server (Node.js)
极简服务器(Node.js)
typescript
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
app.get('/', (c) => {
return c.text('Hello Hono!');
});
const port = 3000;
console.log(`Server is running on http://localhost:${port}`);
serve({
fetch: app.fetch,
port,
});typescript
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
app.get('/', (c) => {
return c.text('Hello Hono!');
});
const port = 3000;
console.log(`Server is running on http://localhost:${port}`);
serve({
fetch: app.fetch,
port,
});Bun Runtime
Bun运行时
typescript
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Bun!'));
export default {
port: 3000,
fetch: app.fetch,
};typescript
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Bun!'));
export default {
port: 3000,
fetch: app.fetch,
};Cloudflare Workers
Cloudflare Workers
typescript
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Cloudflare!'));
export default app;typescript
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Cloudflare!'));
export default app;Routing
路由
HTTP Methods
HTTP方法
typescript
import { Hono } from 'hono';
const app = new Hono();
// Basic routes
app.get('/users', (c) => c.json({ users: [] }));
app.post('/users', (c) => c.json({ created: true }));
app.put('/users/:id', (c) => c.json({ updated: true }));
app.delete('/users/:id', (c) => c.json({ deleted: true }));
app.patch('/users/:id', (c) => c.json({ patched: true }));
// Multiple methods on same path
app.on(['GET', 'POST'], '/multi', (c) => {
return c.text(`Method: ${c.req.method}`);
});
// All methods
app.all('/catch-all', (c) => c.text('Any method'));typescript
import { Hono } from 'hono';
const app = new Hono();
// 基础路由
app.get('/users', (c) => c.json({ users: [] }));
app.post('/users', (c) => c.json({ created: true }));
app.put('/users/:id', (c) => c.json({ updated: true }));
app.delete('/users/:id', (c) => c.json({ deleted: true }));
app.patch('/users/:id', (c) => c.json({ patched: true }));
// 同一路径绑定多个方法
app.on(['GET', 'POST'], '/multi', (c) => {
return c.text(`Method: ${c.req.method}`);
});
// 匹配所有方法
app.all('/catch-all', (c) => c.text('Any method'));Route Parameters
路由参数
typescript
// Path parameters
app.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.json({ userId: id });
});
// Multiple parameters
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param();
return c.json({ postId, commentId });
});
// Optional parameters
app.get('/users/:id?', (c) => {
const id = c.req.param('id');
return c.json({ userId: id ?? 'all' });
});typescript
// 路径参数
app.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.json({ userId: id });
});
// 多参数
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param();
return c.json({ postId, commentId });
});
// 可选参数
app.get('/users/:id?', (c) => {
const id = c.req.param('id');
return c.json({ userId: id ?? 'all' });
});Wildcards and Regex
通配符与正则
typescript
// Wildcard (matches anything after)
app.get('/files/*', (c) => {
return c.text('File handler');
});
// Regex patterns
app.get('/posts/:id{[0-9]+}', (c) => {
// Only matches numeric IDs
const id = c.req.param('id');
return c.json({ postId: Number.parseInt(id) });
});typescript
// 通配符(匹配路径后续所有内容)
app.get('/files/*', (c) => {
return c.text('File handler');
});
// 正则匹配
app.get('/posts/:id{[0-9]+}', (c) => {
// 仅匹配数字ID
const id = c.req.param('id');
return c.json({ postId: Number.parseInt(id) });
});Route Groups
路由分组
typescript
import { Hono } from 'hono';
const app = new Hono();
// API v1 routes
const v1 = new Hono();
v1.get('/users', (c) => c.json({ version: 1, users: [] }));
v1.get('/posts', (c) => c.json({ version: 1, posts: [] }));
// API v2 routes
const v2 = new Hono();
v2.get('/users', (c) => c.json({ version: 2, users: [] }));
v2.get('/posts', (c) => c.json({ version: 2, posts: [] }));
// Mount route groups
app.route('/api/v1', v1);
app.route('/api/v2', v2);typescript
import { Hono } from 'hono';
const app = new Hono();
// API v1路由组
const v1 = new Hono();
v1.get('/users', (c) => c.json({ version: 1, users: [] }));
v1.get('/posts', (c) => c.json({ version: 1, posts: [] }));
// API v2路由组
const v2 = new Hono();
v2.get('/users', (c) => c.json({ version: 2, users: [] }));
v2.get('/posts', (c) => c.json({ version: 2, posts: [] }));
// 挂载路由组
app.route('/api/v1', v1);
app.route('/api/v2', v2);Request Handling
请求处理
Query Parameters
查询参数
typescript
app.get('/search', (c) => {
// Single query param
const query = c.req.query('q');
// With default value
const page = c.req.query('page') ?? '1';
// All query params
const params = c.req.queries();
// { q: ['search'], page: ['1'], tags: ['a', 'b'] }
return c.json({ query, page, params });
});typescript
app.get('/search', (c) => {
// 单个查询参数
const query = c.req.query('q');
// 带默认值
const page = c.req.query('page') ?? '1';
// 所有查询参数
const params = c.req.queries();
// { q: ['search'], page: ['1'], tags: ['a', 'b'] }
return c.json({ query, page, params });
});Request Body
请求体
typescript
// JSON body
app.post('/users', async (c) => {
const body = await c.req.json();
return c.json({ received: body });
});
// Form data
app.post('/upload', async (c) => {
const formData = await c.req.formData();
const name = formData.get('name');
return c.text(`Received: ${name}`);
});
// Text body
app.post('/webhook', async (c) => {
const text = await c.req.text();
return c.text('OK');
});
// Parse once pattern
app.post('/data', async (c) => {
const body = await c.req.parseBody();
// Automatically detects JSON, form data, or multipart
return c.json(body);
});typescript
// JSON请求体
app.post('/users', async (c) => {
const body = await c.req.json();
return c.json({ received: body });
});
// 表单数据
app.post('/upload', async (c) => {
const formData = await c.req.formData();
const name = formData.get('name');
return c.text(`Received: ${name}`);
});
// 文本请求体
app.post('/webhook', async (c) => {
const text = await c.req.text();
return c.text('OK');
});
// 自动解析模式
app.post('/data', async (c) => {
const body = await c.req.parseBody();
// 自动识别JSON、表单数据或多部分表单
return c.json(body);
});Headers
请求头
typescript
app.get('/headers', (c) => {
// Get single header
const auth = c.req.header('Authorization');
// Get all headers
const headers = c.req.raw.headers;
// Set response headers
c.header('X-Custom-Header', 'value');
c.header('Cache-Control', 'no-cache');
return c.json({ auth });
});typescript
app.get('/headers', (c) => {
// 获取单个请求头
const auth = c.req.header('Authorization');
// 获取所有请求头
const headers = c.req.raw.headers;
// 设置响应头
c.header('X-Custom-Header', 'value');
c.header('Cache-Control', 'no-cache');
return c.json({ auth });
});Response Types
响应类型
JSON Response
JSON响应
typescript
app.get('/users', (c) => {
return c.json({
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
});
});
// With status code
app.post('/users', (c) => {
return c.json({ created: true }, 201);
});
// Pretty print in development
app.get('/debug', (c) => {
return c.json({ data: 'value' }, 200, {
'Content-Type': 'application/json; charset=utf-8',
});
});typescript
app.get('/users', (c) => {
return c.json({
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
});
});
// 带状态码
app.post('/users', (c) => {
return c.json({ created: true }, 201);
});
// 开发环境格式化输出
app.get('/debug', (c) => {
return c.json({ data: 'value' }, 200, {
'Content-Type': 'application/json; charset=utf-8',
});
});Other Responses
其他响应类型
typescript
// Text
app.get('/health', (c) => c.text('OK'));
// HTML
app.get('/home', (c) => {
return c.html('<h1>Welcome</h1>');
});
// Redirect
app.get('/old', (c) => c.redirect('/new'));
app.get('/external', (c) => c.redirect('https://example.com', 301));
// Stream
app.get('/stream', (c) => {
return c.stream(async (stream) => {
for (let i = 0; i < 10; i++) {
await stream.writeln(`Line ${i}`);
await new Promise((resolve) => setTimeout(resolve, 100));
}
});
});
// Not Found
app.get('/missing', (c) => c.notFound());typescript
// 文本响应
app.get('/health', (c) => c.text('OK'));
// HTML响应
app.get('/home', (c) => {
return c.html('<h1>Welcome</h1>');
});
// 重定向
app.get('/old', (c) => c.redirect('/new'));
app.get('/external', (c) => c.redirect('https://example.com', 301));
// 流式响应
app.get('/stream', (c) => {
return c.stream(async (stream) => {
for (let i = 0; i < 10; i++) {
await stream.writeln(`Line ${i}`);
await new Promise((resolve) => setTimeout(resolve, 100));
}
});
});
// 404响应
app.get('/missing', (c) => c.notFound());Middleware
中间件
Built-in Middleware
内置中间件
typescript
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { prettyJSON } from 'hono/pretty-json';
import { secureHeaders } from 'hono/secure-headers';
const app = new Hono();
// Logger
app.use('*', logger());
// CORS
app.use('*', cors({
origin: ['http://localhost:3000', 'https://example.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
// Pretty JSON in development
if (process.env.NODE_ENV === 'development') {
app.use('*', prettyJSON());
}
// Security headers
app.use('*', secureHeaders());typescript
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { prettyJSON } from 'hono/pretty-json';
import { secureHeaders } from 'hono/secure-headers';
const app = new Hono();
// 日志中间件
app.use('*', logger());
// CORS中间件
app.use('*', cors({
origin: ['http://localhost:3000', 'https://example.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
// 开发环境格式化JSON
if (process.env.NODE_ENV === 'development') {
app.use('*', prettyJSON());
}
// 安全响应头中间件
app.use('*', secureHeaders());Custom Middleware
自定义中间件
typescript
// Simple middleware
app.use('*', async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`);
await next();
});
// Auth middleware
const authMiddleware = async (c, next) => {
const token = c.req.header('Authorization');
if (!token) {
return c.json({ error: 'Unauthorized' }, 401);
}
// Verify token (example)
if (token !== 'Bearer valid-token') {
return c.json({ error: 'Invalid token' }, 403);
}
// Store user in context
c.set('user', { id: 1, name: 'Alice' });
await next();
};
// Apply to specific routes
app.use('/api/*', authMiddleware);
app.get('/api/profile', (c) => {
const user = c.get('user');
return c.json({ user });
});typescript
// 简单中间件
app.use('*', async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`);
await next();
});
// 鉴权中间件
const authMiddleware = async (c, next) => {
const token = c.req.header('Authorization');
if (!token) {
return c.json({ error: 'Unauthorized' }, 401);
}
// 验证token(示例)
if (token !== 'Bearer valid-token') {
return c.json({ error: 'Invalid token' }, 403);
}
// 将用户信息存入上下文
c.set('user', { id: 1, name: 'Alice' });
await next();
};
// 应用到指定路由
app.use('/api/*', authMiddleware);
app.get('/api/profile', (c) => {
const user = c.get('user');
return c.json({ user });
});Timing Middleware
响应计时中间件
typescript
const timingMiddleware = async (c, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
c.header('X-Response-Time', `${ms}ms`);
};
app.use('*', timingMiddleware);typescript
const timingMiddleware = async (c, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
c.header('X-Response-Time', `${ms}ms`);
};
app.use('*', timingMiddleware);Validation with Zod
Zod数据校验
Setup
配置
bash
pnpm add zod @hono/zod-validatorbash
pnpm add zod @hono/zod-validatorRequest Validation
请求校验
typescript
import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';
// Define schemas
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(120).optional(),
});
const idSchema = z.object({
id: z.string().regex(/^\d+$/),
});
const querySchema = z.object({
page: z.string().regex(/^\d+$/).default('1'),
limit: z.string().regex(/^\d+$/).default('10'),
});
// Validate request body
app.post('/users', zValidator('json', userSchema), async (c) => {
const user = c.req.valid('json');
// user is fully typed: { name: string; email: string; age?: number }
return c.json({ created: true, user }, 201);
});
// Validate path params
app.get('/users/:id', zValidator('param', idSchema), (c) => {
const { id } = c.req.valid('param');
return c.json({ userId: id });
});
// Validate query params
app.get('/users', zValidator('query', querySchema), (c) => {
const { page, limit } = c.req.valid('query');
return c.json({
page: Number.parseInt(page),
limit: Number.parseInt(limit),
users: [],
});
});typescript
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(),
});
const idSchema = z.object({
id: z.string().regex(/^\d+$/),
});
const querySchema = z.object({
page: z.string().regex(/^\d+$/).default('1'),
limit: z.string().regex(/^\d+$/).default('10'),
});
// 校验请求体
app.post('/users', zValidator('json', userSchema), async (c) => {
const user = c.req.valid('json');
// user类型完全安全:{ name: string; email: string; age?: number }
return c.json({ created: true, user }, 201);
});
// 校验路径参数
app.get('/users/:id', zValidator('param', idSchema), (c) => {
const { id } = c.req.valid('param');
return c.json({ userId: id });
});
// 校验查询参数
app.get('/users', zValidator('query', querySchema), (c) => {
const { page, limit } = c.req.valid('query');
return c.json({
page: Number.parseInt(page),
limit: Number.parseInt(limit),
users: [],
});
});Custom Validation Error Handling
自定义校验错误处理
typescript
import { zValidator } from '@hono/zod-validator';
app.post(
'/users',
zValidator('json', userSchema, (result, c) => {
if (!result.success) {
return c.json({
error: 'Validation failed',
details: result.error.flatten(),
}, 400);
}
}),
async (c) => {
const user = c.req.valid('json');
return c.json({ created: true, user }, 201);
}
);typescript
import { zValidator } from '@hono/zod-validator';
app.post(
'/users',
zValidator('json', userSchema, (result, c) => {
if (!result.success) {
return c.json({
error: 'Validation failed',
details: result.error.flatten(),
}, 400);
}
}),
async (c) => {
const user = c.req.valid('json');
return c.json({ created: true, user }, 201);
}
);Error Handling
错误处理
Try-Catch Pattern
捕获异常模式
typescript
app.get('/users/:id', async (c) => {
try {
const id = c.req.param('id');
const user = await db.users.findById(id);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ user });
} catch (error) {
console.error('Error fetching user:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});typescript
app.get('/users/:id', async (c) => {
try {
const id = c.req.param('id');
const user = await db.users.findById(id);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ user });
} catch (error) {
console.error('Error fetching user:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});Global Error Handler
全局错误处理器
typescript
import { HTTPException } from 'hono/http-exception';
app.onError((err, c) => {
console.error(`Error: ${err.message}`);
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);
});
// Throw HTTP exceptions
app.get('/protected', (c) => {
throw new HTTPException(403, { message: 'Forbidden' });
});typescript
import { HTTPException } from 'hono/http-exception';
app.onError((err, c) => {
console.error(`Error: ${err.message}`);
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);
});
// 抛出HTTP异常
app.get('/protected', (c) => {
throw new HTTPException(403, { message: 'Forbidden' });
});404 Handler
404处理器
typescript
app.notFound((c) => {
return c.json({
error: 'Not Found',
path: c.req.path,
}, 404);
});typescript
app.notFound((c) => {
return c.json({
error: 'Not Found',
path: c.req.path,
}, 404);
});Type Safety
类型安全
Typed Context
类型化上下文
typescript
import type { Context } from 'hono';
type Variables = {
user: { id: number; name: string };
};
type Env = {
Variables: Variables;
};
const app = new Hono<Env>();
app.use('/api/*', async (c, next) => {
c.set('user', { id: 1, name: 'Alice' });
await next();
});
app.get('/api/profile', (c) => {
const user = c.get('user'); // Fully typed
return c.json({ user });
});typescript
import type { Context } from 'hono';
type Variables = {
user: { id: number; name: string };
};
type Env = {
Variables: Variables;
};
const app = new Hono<Env>();
app.use('/api/*', async (c, next) => {
c.set('user', { id: 1, name: 'Alice' });
await next();
});
app.get('/api/profile', (c) => {
const user = c.get('user'); // 类型完全安全
return c.json({ user });
});RPC Type Safety (Hono Client)
RPC类型安全(Hono客户端)
typescript
// server.ts
const app = new Hono()
.get('/posts', (c) => c.json({ posts: [] }))
.post('/posts', async (c) => {
const body = await c.req.json();
return c.json({ created: true, post: body }, 201);
});
export type AppType = typeof app;
// client.ts
import { hc } from 'hono/client';
import type { AppType } from './server';
const client = hc<AppType>('http://localhost:3000');
// Fully typed API calls
const res = await client.posts.$get();
const data = await res.json(); // { posts: [] }
await client.posts.$post({ json: { title: 'Hello' } });typescript
// server.ts
const app = new Hono()
.get('/posts', (c) => c.json({ posts: [] }))
.post('/posts', async (c) => {
const body = await c.req.json();
return c.json({ created: true, post: body }, 201);
});
export type AppType = typeof app;
// client.ts
import { hc } from 'hono/client';
import type { AppType } from './server';
const client = hc<AppType>('http://localhost:3000');
// 类型完全安全的API调用
const res = await client.posts.$get();
const data = await res.json(); // { posts: [] }
await client.posts.$post({ json: { title: 'Hello' } });Static Files
静态文件服务
typescript
import { serveStatic } from '@hono/node-server/serve-static';
// Serve from public directory
app.use('/static/*', serveStatic({ root: './' }));
// Serve index.html for SPA
app.get('*', serveStatic({ path: './dist/index.html' }));
// With custom 404
app.use('/assets/*', serveStatic({
root: './',
onNotFound: (path, c) => {
console.log(`${path} is not found`);
},
}));typescript
import { serveStatic } from '@hono/node-server/serve-static';
// 从public目录提供静态文件
app.use('/static/*', serveStatic({ root: './' }));
// 为单页应用提供index.html
app.get('*', serveStatic({ path: './dist/index.html' }));
// 自定义404处理
app.use('/assets/*', serveStatic({
root: './',
onNotFound: (path, c) => {
console.log(`${path} is not found`);
},
}));CORS Configuration
CORS配置
typescript
import { cors } from 'hono/cors';
// Permissive (development)
app.use('*', cors());
// Production config
app.use('/api/*', cors({
origin: (origin) => {
// Dynamic origin validation
return origin.endsWith('.example.com') ? origin : 'https://example.com';
},
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowHeaders: ['Content-Type', 'Authorization'],
exposeHeaders: ['X-Total-Count'],
credentials: true,
maxAge: 600,
}));typescript
import { cors } from 'hono/cors';
// 宽松配置(开发环境)
app.use('*', cors());
// 生产环境配置
app.use('/api/*', cors({
origin: (origin) => {
// 动态验证源
return origin.endsWith('.example.com') ? origin : 'https://example.com';
},
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowHeaders: ['Content-Type', 'Authorization'],
exposeHeaders: ['X-Total-Count'],
credentials: true,
maxAge: 600,
}));Environment Variables
环境变量
typescript
import { Hono } from 'hono';
type Bindings = {
DATABASE_URL: string;
API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get('/config', (c) => {
// Access environment variables
const dbUrl = c.env.DATABASE_URL;
const apiKey = c.env.API_KEY;
return c.json({ configured: !!dbUrl && !!apiKey });
});
// Node.js
import { serve } from '@hono/node-server';
serve({
fetch: app.fetch,
port: 3000,
});
// Access via process.env in Node.js
const dbUrl = process.env.DATABASE_URL;typescript
import { Hono } from 'hono';
type Bindings = {
DATABASE_URL: string;
API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get('/config', (c) => {
// 访问环境变量
const dbUrl = c.env.DATABASE_URL;
const apiKey = c.env.API_KEY;
return c.json({ configured: !!dbUrl && !!apiKey });
});
// Node.js环境
import { serve } from '@hono/node-server';
serve({
fetch: app.fetch,
port: 3000,
});
// 在Node.js中通过process.env访问
const dbUrl = process.env.DATABASE_URL;Testing
测试
typescript
import { describe, it, expect } from 'vitest';
import { Hono } from 'hono';
describe('API Tests', () => {
const app = new Hono();
app.get('/hello', (c) => c.json({ message: 'Hello' }));
it('should return hello message', async () => {
const res = await app.request('/hello');
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ message: 'Hello' });
});
it('should handle POST requests', 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);
const data = await res.json();
expect(data.created).toBe(true);
expect(data.user.name).toBe('Alice');
});
});typescript
import { describe, it, expect } from 'vitest';
import { Hono } from 'hono';
describe('API Tests', () => {
const app = new Hono();
app.get('/hello', (c) => c.json({ message: 'Hello' }));
it('should return hello message', async () => {
const res = await app.request('/hello');
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ message: 'Hello' });
});
it('should handle POST requests', 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);
const data = await res.json();
expect(data.created).toBe(true);
expect(data.user.name).toBe('Alice');
});
});Best Practices
最佳实践
- Use route groups to organize related endpoints into modular routers
- Validate all inputs with Zod for type safety and runtime validation
- Apply middleware sparingly - only use what you need per route
- Set explicit CORS policies for production - never use permissive CORS in prod
- Use typed contexts for variables set in middleware (user, db, etc.)
- Handle errors globally with app.onError() for consistent error responses
- Return appropriate status codes - 201 for created, 204 for no content, etc.
- Use HTTP exceptions instead of manually creating error responses
- Test with app.request() - Hono's built-in testing utility
- Leverage RPC types for type-safe client-server communication
- Keep middleware async - always await next() in custom middleware
- Use environment variables for configuration, never hardcode secrets
- 使用路由分组:将相关接口组织为模块化路由
- 校验所有输入:使用Zod实现类型安全和运行时校验
- 按需使用中间件:仅为需要的路由应用必要的中间件
- 设置明确的CORS策略:生产环境绝不使用宽松的CORS配置
- 使用类型化上下文:存储中间件中设置的变量(用户、数据库等)
- 全局处理错误:通过app.onError()实现统一的错误响应格式
- 返回合适的状态码:创建成功返回201,无内容返回204等
- 使用HTTPException:替代手动创建错误响应
- 使用app.request()测试:Hono内置的测试工具
- 利用RPC类型:实现类型安全的客户端-服务端通信
- 中间件保持异步:自定义中间件中始终await next()
- 使用环境变量配置:绝不硬编码敏感信息
Anti-Patterns
反模式
- ❌ Applying logger middleware after routes (won't log those routes)
- ❌ Forgetting to await next() in middleware (breaks middleware chain)
- ❌ Using cors() only on specific routes (preflight requests need global CORS)
- ❌ Parsing request body multiple times (cache result after first parse)
- ❌ Not validating path parameters (always validate user input)
- ❌ Returning without status code for errors (explicit is better)
- ❌ Using any type instead of proper Hono generics
- ❌ Hardcoding origins in CORS config (use environment variables)
- ❌ Missing error handlers (leads to unhandled promise rejections)
- ❌ Not using HTTPException for known errors (inconsistent error format)
- ❌ Setting headers after returning response (headers already sent)
- ❌ Forgetting to export app for runtime adapters
- ❌ 在路由之后应用日志中间件(无法记录这些路由的请求)
- ❌ 中间件中忘记await next()(中断中间件链)
- ❌ 仅在特定路由上应用cors()(预检请求需要全局CORS配置)
- ❌ 多次解析请求体(首次解析后缓存结果)
- ❌ 不校验路径参数(始终校验用户输入)
- ❌ 错误响应不指定状态码(明确优于模糊)
- ❌ 使用any类型而非Hono泛型
- ❌ 在CORS配置中硬编码源(使用环境变量)
- ❌ 缺失错误处理器(导致未处理的Promise拒绝)
- ❌ 已知错误不使用HTTPException(错误格式不一致)
- ❌ 返回响应后设置头信息(响应头已发送)
- ❌ 忘记为运行时适配器导出应用
Feedback Loops
反馈验证
Testing endpoints:
bash
undefined测试接口:
bash
undefinedTest with curl
使用curl测试
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":"alice@example.com"}'
-H "Content-Type: application/json"
-d '{"name":"Alice","email":"alice@example.com"}'
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":"alice@example.com"}'
-H "Content-Type: application/json"
-d '{"name":"Alice","email":"alice@example.com"}'
Test with httpie (better formatting)
使用httpie测试(格式更友好)
http GET localhost:3000/api/users
http POST localhost:3000/api/users name=Alice email=alice@example.com
**Performance testing:**
```bashhttp GET localhost:3000/api/users
http POST localhost:3000/api/users name=Alice email=alice@example.com
**性能测试:**
```bashUse autocannon for load testing
使用autocannon进行负载测试
pnpm add -D autocannon
npx autocannon -c 100 -d 10 http://localhost:3000/api/users
pnpm add -D autocannon
npx autocannon -c 100 -d 10 http://localhost:3000/api/users
Check response times and throughput
检查响应时间和吞吐量
Target: <10ms p99 latency for simple endpoints
目标:简单接口p99延迟<10ms
**Validation testing:**
```bash
**校验测试:**
```bashTest invalid data returns 400
测试无效数据是否返回400
curl -X POST http://localhost:3000/api/users
-H "Content-Type: application/json"
-d '{"name":"","email":"invalid"}'
-H "Content-Type: application/json"
-d '{"name":"","email":"invalid"}'
curl -X POST http://localhost:3000/api/users
-H "Content-Type: application/json"
-d '{"name":"","email":"invalid"}'
-H "Content-Type: application/json"
-d '{"name":"","email":"invalid"}'
Should return validation error with details
应返回包含详情的校验错误
undefinedundefined