全栈类型共享:从 Prisma 到前端,一劳永逸解决接口不一致
📝 引言
📌 场景/痛点
在传统的开发模式中,你是否经历过这种痛苦的循环:
- 后端:修改了数据库
User表,加了phone字段。 - 接口:忘记通知前端。
- 前端:解析
getUser接口时,拿到的数据里多了一个字段,但因为没改类型定义,忽略了这个重要信息,导致功能 bug。
维护两份类型定义(一份后端,一份前端)是反人类的。
在 2026 年,我们的目标应该是:定义一次,到处运行。后端的数据库模型定义,应该直接成为前端的类型定义。
✨ 最终效果
掌握本文后,你将建立一条全栈类型流水线。当你在后端修改 Schema 时,前端会自动感知到类型变化,甚至在编译时就发现接口不匹配。

📖 内容概览
本文将带你构建全栈类型共享体系:
- 真理之源:使用 Prisma 定义数据模型。
- 类型导出:如何将生成的类型安全地暴露给前端。
- 数据传输 (DTO):过滤敏感字段(如密码)的最佳实践。
- 实战:在 Next.js/React 中直接使用数据库类型。
🛠️ 正文
1. 环境准备
假设我们使用 Prisma 作为 ORM(这是 2026 年最流行的选择)。
npm install prisma @prisma/client
初始化 Prisma:
npx prisma init
2. 步骤一:定义 Schema(后端)
在 schema.prisma 中定义数据模型。这是全栈类型的“源头”。
// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
password String // 敏感字段
role Role @default(USER)
createdAt DateTime @default(now())
}
enum Role {
USER
ADMIN
}
运行生成命令,TS 类型会自动生成:
npx prisma generate
此时,src/generated/prisma/models/User.ts 里已经生成了 User 类型。
3. 步骤二:类型清洗与导出
⚠️ 警告:不要直接把数据库的 User 类型暴露给前端!因为里面包含 password 等敏感字段,且包含大量内部元数据。
我们需要定义 DTO (Data Transfer Object)。
src/shared/types.ts (Monorepo 共享包 或 src/types)
import { User as PrismaUser } from '@prisma/client';
// 方法 A:使用 Omit 排除敏感字段
export type UserDTO = Omit<PrismaUser, 'password'>;
// 方法 B:使用 Pick 只保留需要的字段
export type UserSafeInfo = Pick<PrismaUser, 'id' | 'email' | 'role'>;
4. 步骤三:前端直接使用
假设这是一个 Monorepo 项目,或者通过 npm link 引用了后端类型。前端(React/Vue)现在可以直接导入并使用 UserDTO。
// frontend/src/components/UserCard.tsx
import { UserDTO } from '@my-monorepo/shared-types'; // 导入共享类型
interface Props {
user: UserDTO; // ✅ 类型安全
}
export const UserCard = ({ user }: Props) => {
return (
{user.email}
Role: {user.role}
{/* ✅ IDE 智能提示,且不允许访问 password (因为被 Omit 了) */}
{/* {user.password}
❌ Error: Property 'password' does not exist */}
);
};
5. 进阶:使用 Prisma Generator 自动生成 DTO
手动写 Omit 依然有点麻烦。在 2026 年,我们可以利用 Prisma 的 Generator 插件(如 prisma-types-generator)自动生成对应的 DTO 类型。
schema.prisma 配置:
generator dto {
provider = "prisma-types-generator"
output = "../src/shared/types"
}
运行 npx prisma generate 后,它会自动生成 UserDTO、UserCreateInput 等类型,无需手写。
6. 更高级的方案:tRPC
如果你真的想达到“全自动”的境界,应该使用 tRPC。
- tRPC 允许你直接在后端定义 procedure(类似 API 函数)。
- 前端调用
trpc.user.get.query()时,TypeScript 会自动推导出返回值类型,完全不需要手动定义interface。
这就是 2026 年全栈开发的终极形态:API 只是函数调用,类型由编译器自动携带。
❓ 常见问题
Q1: 前后端分离项目(非 Monorepo)怎么共享类型?
A:
- 发布到 npm:把类型文件打包成一个
@my-org/types包,发布到私有 npm 仓库。 - OpenAPI/Swagger:后端生成 Swagger 文档,前端用
openapi-typescript-codegen自动生成 TS 类型。这种方式虽然繁琐,但不依赖 Monorepo。
Q2: 为什么不直接把 User 模型传给前端?
A: 安全隐患。数据库模型通常包含内部字段(如 password, deletedAt, hash)。直接暴露给前端极不安全。永远在后端输出层做一次类型过滤 (DTO)。
Q3: 前端修改了类型,后端没改怎么办?
A: 如果使用了 tRPC 或严格类型导入,前端修改类型后,如果后端返回的数据结构变了(比如 API 返回了 string 但前端期望 number),TypeScript 编译会直接报错。这种“编译时报错”替代了“运行时崩溃”,极大提高了稳定性。
🎯 总结
全栈类型共享是 TypeScript 生态的最大优势。本文我们掌握了:
- 真理之源:数据库 Schema 是类型的起点(如 Prisma)。
- 安全第一:使用 DTO (
Omit/Pick) 过滤敏感字段。 - Monorepo:通过文件共享或包引用实现前后端类型打通。
- 终极形态:tRPC 让 API 调用像本地函数调用一样安全。
🚀 下期预告:
全栈开发中,我们经常需要区分“技术上相同的 ID”和“业务上不同的 ID”。比如 UserId 和 OrderId 都是 number,传错了怎么办?
下一篇文章我们将深入 《ESM 与 Node.js 互操作性终极指南》,用类型系统杜绝低级错误。
💬 互动环节:
你的团队前后端联调是靠“聊天记录”还是靠“类型定义”?
如果觉得文章让你看到了全栈开发的未来,请点赞👍、收藏⭐、关注👀,下期更精彩!






