hono

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hono

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 hono
Hono是一款轻量、高性能的Web框架,用于构建API和服务端应用。Hono 4.x拥有统一的API,可在Node.js、Bun、Deno、Cloudflare Workers等多种运行时环境中运行。
版本:4.x 安装
pnpm add hono

Workflows

工作流程

Creating a basic API:
  1. Create Hono app instance:
    const app = new Hono()
  2. Define routes with HTTP methods
  3. Add middleware (CORS, logger, error handling)
  4. Export app for runtime adapter
  5. Test endpoints with curl or Postman
Adding validation:
  1. Install Zod:
    pnpm add zod @hono/zod-validator
  2. Define schemas with Zod
  3. Apply validator middleware to routes
  4. Handle validation errors
  5. Type-safe request access
创建基础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. 为路由应用校验中间件
  4. 处理校验错误
  5. 类型安全的请求访问

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-validator
bash
pnpm add zod @hono/zod-validator

Request 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
undefined

Test 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"}'
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"}'

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:**
```bash
http GET localhost:3000/api/users http POST localhost:3000/api/users name=Alice email=alice@example.com

**性能测试:**
```bash

Use 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

**校验测试:**
```bash

Test invalid data returns 400

测试无效数据是否返回400

curl -X POST http://localhost:3000/api/users
-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"}'

Should return validation error with details

应返回包含详情的校验错误

undefined
undefined