内容型 Web 的主路径是:请求进来,查询数据库,返回 HTML 或 JSON。偶尔有一条评论通知。这跟「流式对话、对称 RPC」不是同一个问题。

Hono 把路由本身当成类型来源。注册过的路径,客户端能直接推出来。

TypeScriptsrc/server.ts
1import { Hono } from "hono";2import { zValidator } from "@hono/zod-validator";3import { z } from "zod";4 5export const app = new Hono()6  .get("/api/posts", async (c) => {7    const posts = await listPublished();8    return c.json({ posts });9  })10  .get("/api/posts/:slug", async (c) => {11    const post = await getBySlug(c.req.param("slug"));12    if (!post) return c.notFound();13    return c.json({ post });14  })15  .post(16    "/api/posts",17    zValidator("json", z.object({18      title: z.string().min(1),19      body: z.string().min(1),20      topic: z.string(),21    })),22    async (c) => {23      const input = c.req.valid("json");24      const post = await createPost(input);25      return c.json({ post }, 201);26    },27  );28 29export type AppType = typeof app;

前端不必再手写路径字符串:

TypeScriptsrc/lib/api.ts
1import { hc } from "hono/client";2import type { AppType } from "../server";3 4export const api = hc<AppType>("/");5 6const { posts } = await (await api.api.posts.$get()).json();

什么时候不要用它

纯静态 Markdown 博客连 API 层都可以省。强 SEO 的站点,页面壳和 /api 可以分开。需要两端互相流式调函数,再考虑更重的 RPC。

Diff
1- export const gateway = createCustomRpc(host)2- client.invoke("write_post", payload)3+ const res = await api.api.posts.$post({ json: payload })4+ if (!res.ok) throw new Error(await res.text())
类型安全不等于协议升级。能用 HTTP 说清楚的事,就用 HTTP 说。

请求进来后仍要会查库。慢查询怎么看规划,见 sql-explain。前端状态机用 判别联合 收口,比在组件里猜 data && !error 干净。取消从路由传到 goroutine,Go 的写法在 go-select

请求-响应的站点,显式 HTTP 路由就够。类型安全来自路由表,不必再发明一层协议。