全栈接线
连接前端、API 和数据库,让按钮点击往返 DB 并返回。
为什么
'全栈接线'是产品变真实的时刻——用户点击、数据流到 DB、结果显示屏幕。此前你有漂亮 UI 模型和 DB schema 但无产品。接线是多数 bug 所在:前端发错字段名、API 不验证、ORM 静默丢列、用户看通用错误。有纪律接线用端到端类型安全(前端类型从 API schema 生成、API 类型从 DB schema),重命名是全栈编译错误。没有它,三层漂移且每次集成是调试会话。
何时使用
UI 和 DB schema 存在后,第一个用户功能之前。约 2-4 小时。仅纯静态站可跳过。
如何做
1. 从服务端生成 API 类型并在客户端导入
- tRPC:类型从服务端 router 自动流到客户端
- REST:openapi-typescript 从 OpenAPI spec 生成类型
- 永不手写客户端类型复制服务端的
2. 用带类型查询的 ORM(Prisma、Drizzle)让 DB 结果有类型
- prisma.user.findUnique 返回类型化 User,非 any
- schema 变更 → prisma generate → 每个坏查询的类型错误
3. 先端到端接一个功能作模板
- 按钮 → fetch /api/items → prisma.item.create → 返回类型化 Item → 渲染
- 这成每个未来功能的复制粘贴模式
4. 加用 zod 验证输入的 server action 或 route handler
- const schema = z.object({ name: z.string().min(1) })
- 碰 DB 前解析;返回 UI 可显示的类型化错误
5. 测往返:Playwright 测试点按钮并断言 DB 行存在
- 这是唯一抓跨三层接线 bug 的测试示例
// 服务端:app/api/items/route.ts(Next.js + Prisma + zod)
import { prisma } from "@/lib/prisma";
import { z } from "zod";
const schema = z.object({ name: z.string().min(1), qty: z.coerce.number().int().positive() });
export async function POST(req: Request) {
const input = schema.parse(await req.json()); // 非法即抛
const item = await prisma.item.create({ data: input }); // 类型化 Item
return Response.json(item); // 类型化响应
}
// 客户端:类型从服务端流
import type { Item } from "@prisma/client"; // 单一事实源
async function createItem(input: { name: string; qty: number }): Promise<Item> {
const res = await fetch("/api/items", { method: "POST", body: JSON.stringify(input) });
return res.json();
}常见问题
- ⚠手写客户端类型与服务端漂移,字段重命名静默坏客户端。
- ⚠服务端跳过输入验证因'表单验证了',直接 API 调用绕过所有检查。
- ⚠用 prisma.$queryRaw 模板字符串,丢类型安全并开 SQL 注入。
- ⚠无端到端测试,接线 bug(错字段名、缺列)仅手工 QA 才现。
最佳实践
- ✓用 tRPC 或生成客户端让类型从服务端流到客户端零手工同步。
- ✓服务端用 zod 验证,从 zod schema 推客户端类型(z.infer<typeof schema>)。
- ✓保持一个功能端到端作'黄金路径'每新功能复制— —一致性胜新颖。
- ✓功能'完成'前写 Playwright e2e 测试,接线 bug 立即现。