跳转至

第 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:fetchRequestResponseURLcrypto


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 助手可以安全调用外部工具:

Bash
pnpm add @modelcontextprotocol/sdk
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