第 9 章:Edge Runtime 与 Hono¶
学习时间:4 小时 | 难度:⭐⭐⭐⭐ 高级 | 前置知识:第 6 章,基础 HTTP 知识
本章概览¶
Edge Runtime 是 2025 年后端架构的重要趋势:在全球 CDN 节点运行代码,延迟接近用户,成本按请求计费。Hono 是专为 Edge 环境设计的 TypeScript 框架,API 设计优雅,性能极致。
学习目标:
- 理解 Edge Runtime 与 Node.js 的区别和限制
- 掌握 Hono 的路由、中间件和 RPC 客户端
- 在 Cloudflare Workers 上部署 TypeScript API
- 构建 MCP(Model Context Protocol)服务器
- 理解 Deno Deploy 和 Vercel Edge Functions
9.1 Edge Runtime 基础¶
Text Only
传统 Node.js 服务器:
用户 → 单个数据中心服务器 → 延迟 200-500ms
Edge Runtime:
用户 → 全球最近的 CDN 节点运行代码 → 延迟 10-50ms
Edge Runtime 的限制(相比 Node.js): - ❌ 无法使用 fs 文件系统 API - ❌ 无法使用 crypto.createCipher(只有 Web Crypto API) - ❌ 启动时间限制(Cloudflare Workers:1ms CPU 启动时间) - ❌ 内存限制(Cloudflare Workers:128MB) - ✅ 支持 Web 标准 API:fetch、Request、Response、URL、crypto
9.2 Hono 框架入门¶
Bash
# Cloudflare Workers 项目
pnpm create hono@latest my-api -- --template cloudflare-workers
# 或 Node.js 项目(Hono 也支持 Node.js)
pnpm create hono@latest my-api -- --template nodejs
TypeScript
// src/index.ts — Hono 最基础示例
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { prettyJSON } from "hono/pretty-json";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
// Cloudflare Workers 环境变量类型(在 wrangler.toml 中配置)
type Bindings = {
DATABASE: D1Database; // Cloudflare D1(SQLite)
KV: KVNamespace; // Cloudflare KV(键值存储)
AI: Ai; // Cloudflare Workers AI
JWT_SECRET: string; // 环境变量(secret)
};
// 上下文变量(中间件传递的数据)
type Variables = {
userId?: string;
role?: "admin" | "user";
};
// 创建 Hono 实例(泛型参数提供完整类型支持)
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();
// 全局中间件
app.use("*", logger());
app.use("*", prettyJSON());
app.use("/api/*", cors({
origin: ["https://myapp.com", "http://localhost:3000"],
credentials: true,
}));
// 路由
app.get("/", (c) => c.json({ message: "Hello from Edge!", runtime: "cf-workers" }));
app.get("/health", (c) => c.json({ status: "ok", timestamp: new Date().toISOString() }));
export default app;
9.3 Hono 完整 API 示例¶
TypeScript
// src/routes/posts.ts — Hono 博客 API
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { jwt, sign, verify } from "hono/jwt";
import { z } from "zod";
import type { Bindings, Variables } from "../types";
const posts = new Hono<{ Bindings: Bindings; Variables: Variables }>();
// Schema 定义
const CreatePostSchema = z.object({
title: z.string().min(5).max(200),
content: z.string().min(20),
tags: z.array(z.string()).max(5).optional(),
});
const PostIdSchema = z.object({
id: z.string().uuid(),
});
// 认证中间件
const authMiddleware = jwt({
secret: (c) => c.env.JWT_SECRET, // 从 Cloudflare Bindings 获取密钥
});
// 设置用户上下文的中间件
posts.use("/", async (c, next) => {
const payload = c.get("jwtPayload");
if (payload) {
c.set("userId", payload.sub as string);
c.set("role", payload.role as "admin" | "user");
}
await next();
});
// GET /api/posts — 查询帖子列表
posts.get("/", async (c) => {
const { page = "1", limit = "10" } = c.req.query();
// 使用 Cloudflare D1 数据库
const result = await c.env.DATABASE.prepare(
"SELECT id, title, author_id, created_at FROM posts WHERE published = 1 LIMIT ? OFFSET ?"
).bind(Number(limit), (Number(page) - 1) * Number(limit)).all();
return c.json({
posts: result.results,
page: Number(page),
});
});
// POST /api/posts — 创建帖子(需要认证)
posts.post(
"/",
authMiddleware, // 1. JWT 验证中间件
zValidator("json", CreatePostSchema), // 2. 请求体验证(Zod)
async (c) => {
const body = c.req.valid("json"); // body 类型完全推断自 CreatePostSchema
const userId = c.get("userId")!;
const id = crypto.randomUUID();
await c.env.DATABASE.prepare(
"INSERT INTO posts (id, title, content, author_id, created_at) VALUES (?, ?, ?, ?, ?)"
).bind(id, body.title, body.content, userId, new Date().toISOString()).run();
return c.json({ id, message: "Post created" }, 201);
}
);
// DELETE /api/posts/:id
posts.delete(
"/:id",
authMiddleware,
zValidator("param", PostIdSchema),
async (c) => {
const { id } = c.req.valid("param");
const userId = c.get("userId")!;
const role = c.get("role");
// 检查权限:只有帖子作者或管理员可以删除
const post = await c.env.DATABASE.prepare(
"SELECT author_id FROM posts WHERE id = ?"
).bind(id).first<{ author_id: string }>();
if (!post) return c.json({ error: "Post not found" }, 404);
if (post.author_id !== userId && role !== "admin") {
return c.json({ error: "Forbidden" }, 403);
}
await c.env.DATABASE.prepare("DELETE FROM posts WHERE id = ?").bind(id).run();
return c.body(null, 204);
}
);
export { posts };
9.4 Hono RPC — 类型安全的客户端¶
Hono 的 RPC 功能类似于 tRPC,但更轻量。路由类型可以在前端复用:
TypeScript
// src/routes/items.ts — 定义 RPC 路由
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const itemSchema = z.object({
name: z.string(),
price: z.number().positive(),
});
const app = new Hono()
.get("/", (c) => c.json({ items: [{ id: "1", name: "Apple", price: 1.5 }] }))
.post("/", zValidator("json", itemSchema), async (c) => {
const item = c.req.valid("json"); // item: { name: string; price: number }
return c.json({ id: crypto.randomUUID(), ...item }, 201);
})
.get("/:id", (c) => c.json({ id: c.req.param("id"), name: "Apple", price: 1.5 }));
export type ItemRoutes = typeof app; // 导出类型!
export default app;
// client.ts — 前端(任何环境)使用 Hono RPC 客户端
import { hc } from "hono/client";
import type { ItemRoutes } from "./routes/items";
// 创建类型安全的客户端
const client = hc<ItemRoutes>("https://my-api.workers.dev");
// 完整类型推断!
const res = await client.items.$get();
const { items } = await res.json(); // items 类型推断正确
const createRes = await client.items.$post({
json: { name: "Banana", price: 0.8 },
});
const newItem = await createRes.json(); // 完整类型
9.5 构建 MCP 服务器¶
Model Context Protocol(MCP)是 Anthropic 定义的 AI 工具协议,让 AI 助手可以安全调用外部工具:
TypeScript
// src/mcp-server.ts — 用 Hono + Cloudflare Workers 运行 MCP 服务器
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import { Hono } from "hono";
// 创建 MCP 服务器实例
const mcpServer = new McpServer({
name: "TypeScript Learning Assistant",
version: "1.0.0",
});
// 注册工具
mcpServer.tool(
"check-typescript-types",
"检查一段 TypeScript 代码的类型错误",
{
code: z.string().describe("要检查的 TypeScript 代码"),
strict: z.boolean().default(true).describe("是否启用严格模式"),
},
async ({ code, strict }) => {
// 调用 TypeScript compiler API 检查类型
const errors = await checkTypes(code, { strict });
return {
content: [{
type: "text",
text: errors.length === 0
? "✅ 类型检查通过,没有错误"
: `❌ 发现 ${errors.length} 个类型错误:\n${errors.map((e) => `- ${e}`).join("\n")}`,
}],
};
}
);
mcpServer.tool(
"search-npm-types",
"搜索 npm 包的 TypeScript 类型定义",
{
packageName: z.string().describe("npm 包名"),
},
async ({ packageName }) => {
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
const data = await response.json() as { types?: string; typings?: string };
const hasTypes = data.types || data.typings;
const atTypesResponse = await fetch(`https://registry.npmjs.org/@types/${packageName}/latest`);
const hasAtTypes = atTypesResponse.ok;
return {
content: [{
type: "text",
text: hasTypes
? `✅ ${packageName} 自带类型定义(${data.types || data.typings})`
: hasAtTypes
? `✅ 可用 @types/${packageName} 安装类型定义(pnpm add -D @types/${packageName})`
: `⚠️ ${packageName} 没有官方类型定义,可能需要手写 .d.ts 文件`,
}],
};
}
);
// 注册资源(提供上下文数据给 AI)
mcpServer.resource(
"typescript-cheatsheet",
"typescript://cheatsheet",
"TypeScript 常用语法速查表",
async () => ({
contents: [{
uri: "typescript://cheatsheet",
text: `
# TypeScript 速查表
## 基础类型
string | number | boolean | null | undefined | any | unknown | never
## 工具类型
Partial<T> | Required<T> | Readonly<T>
Pick<T, K> | Omit<T, K>
Record<K, V> | Extract<T, U> | Exclude<T, U>
`,
}],
})
);
// 用 Hono 暴露 HTTP 端点
const app = new Hono();
app.post("/mcp", async (c) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
await mcpServer.connect(transport);
return transport.handleRequest(c.req.raw, new Response());
});
app.get("/mcp", (c) => c.json({
name: "TypeScript Learning MCP Server",
version: "1.0.0",
tools: ["check-typescript-types", "search-npm-types"],
}));
export default app;
9.6 部署到 Cloudflare Workers¶
TOML
# wrangler.toml
name = "my-ts-api"
main = "src/index.ts"
compatibility_date = "2025-03-01"
compatibility_flags = ["nodejs_compat"]
[[d1_databases]]
binding = "DATABASE"
database_name = "my-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
[vars]
NODE_ENV = "production"
[secrets]
JWT_SECRET = "your-secret-here" # 用 wrangler secret put JWT_SECRET 设置
Bash
# 开发服务器(本地模拟 Cloudflare 环境)
pnpm wrangler dev
# 部署到生产
pnpm wrangler deploy
# 查看日志
pnpm wrangler tail
📌 本章小结¶
| 技术 | 作用 | 延迟 |
|---|---|---|
| Cloudflare Workers | Edge 计算平台,全球 300+ 节点 | ~10ms |
| Hono | 极致轻量的 TypeScript HTTP 框架(<14KB) | — |
| Cloudflare D1 | Edge 边缘 SQLite 数据库 | — |
| MCP 协议 | AI 工具调用标准协议 | — |
| Hono RPC | 前后端共享类型(类 tRPC) | — |
Hono 4.x · Cloudflare Workers · MCP Protocol · 2025