Wasp 数据模型实战:用 Entity 与 Prisma Schema 构建应用的数据库基石
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
导读
Entity(实体)是 Wasp 应用数据模型的根基——它定义了数据库中的表结构与关系。Wasp 基于 Prisma ORM 实现全部数据库能力,你在项目根目录的schema.prisma文件中以 Prisma Schema Language 声明模型,Wasp 会将其识别为 Entity 并自动生成类型安全的数据库访问代码。读完本文,你将掌握在 Wasp 项目中定义 Entity、通过wasp db migrate-dev同步数据库、在 Operations 与自定义服务端代码中使用 Entity 的完整流程,并能读懂仓库中真实示例(如 kitchen-sink、TodoAppTs)的数据模型设计。
Entity 是什么:数据模型的最小单元
在 Wasp 中,一个 Entity 就对应数据库中的一张表。官方文档对它的定义非常直白:
In short, an Entity defines a model in your database.
Wasp 没有自研一套数据库 ORM,而是直接采用业界成熟的 Prisma ORM 作为底层实现,并在其之上做了一层轻薄的抽象。这意味着:
- 你不需要学习任何新的建模语法——用 Prisma 的
schema.prisma文件定义模型和关系即可; - Wasp 会理解并接管这个 Prisma schema 文件,自动读取你定义的所有模型;
- 每个 Prisma
model声明就是一个 Wasp Entity。
关于 Prisma Schema File 与 Wasp 的协作方式,可进一步阅读 Prisma Schema File 文档。
Entity 与 Prisma Model 的概念区分
虽然现阶段"定义一个 Prisma model"是创建 Entity 的唯一途径,但两者在概念层级上并不等同:
- Entity 是 Wasp 的概念,属于更高层的抽象;
- Model 是 Prisma 的概念,指 Prisma Schema 中的
model块声明。
官方文档明确说明,Wasp 未来计划扩展 Entity 的定义方式和能力范围。因此可以这样理解:目前"所有 Prisma model 都是 Entity,所有 Entity 都是 Prisma model",但这种一一对应关系会随着 Wasp 的演进而变化。在阅读 Wasp 文档和源码时,注意区分这两个术语的语境。
schema.prisma:Entity 的定义场所
在你的 Wasp 项目中,schema.prisma文件位于项目根目录,与main.wasp(或新版本中的main.wasp.ts)、src/、tsconfig.json平级:
. ├── main.wasp ... ├── schema.prisma ├── src ├── tsconfig.json └── vite.config.ts以仓库中的真实项目 examples/tutorials/TodoAppTs 为例,其schema.prisma位于examples/tutorials/TodoAppTs/schema.prisma,内容如下:
datasource db { provider = "sqlite" // Wasp requires that the url is set to the DATABASE_URL environment variable. url = env("DATABASE_URL") } // Wasp requires the `prisma-client-js` generator to be present. generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) user User? @relation(fields: [userId], references: [id]) userId Int? }这个文件清楚地展示了 Wasp 项目 schema 的三个组成部分:
datasource块:声明数据库类型(此处为 SQLite)与连接 URL;generator块:声明生成 Prisma Client 的方式(prisma-client-js);model块:声明数据模型,即 Wasp Entity。
Prisma 使用的Prisma Schema Language是一种声明式的、专门为定义模型而设计的简洁语言。本文后面会给出完整示例,你也可以直接参考 Prisma 官方的 schema 概览与语言规范来深入学习(相关内容在文档中均有指引,但本文以下内容已足以支撑你完成 Wasp 项目的数据建模)。
定义你的第一个 Entity:Task 模型全解析
在schema.prisma中声明一个model即可创建一个 Entity。以官方文档的 Task 为例:
model Task { id String @id @default(uuid()) description String isDone Boolean @default(false) }这段声明告诉 Wasp:为 Task 创建一张表,表中包含三列,含义如下:
| 字段 | 类型 | 约束/默认值 | 说明 |
|---|---|---|---|
id | String | @id @default(uuid()) | 主键,数据库自动生成随机唯一 UUID |
description | String | 无 | 存储任务描述 |
isDone | Boolean | @default(false) | 任务完成状态;创建时若未显式赋值,数据库默认写入false |
值得留意的是:字段类型可以按需扩展。对照仓库中 examples/kitchen-sink/schema.prisma,你可以看到更丰富的建模手法:
enum TaskVisibility { PRIVATE LINK_ONLY PUBLIC } model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) user User @relation(fields: [userId], references: [id]) userId Int votes TaskVote[] visibility TaskVisibility @default(PRIVATE) }这里演示了三个进阶能力:autoincrement()自增整数主键、通过@relation(fields: [userId], references: [id])建立多对一关联、以及用enum定义枚举字段并为visibility设置默认值。这些都属于标准 Prisma schema 语法,只要写法合法,Wasp 都能直接支持。
Entity 的 TypeScript 类型:从数据库到代码的类型安全
在 TypeScript 项目中,Wasp 会为每个 Entity 自动暴露对应的类型,你可以直接从wasp/entities模块导入:
import { Task } from 'wasp/entities' const task: Task = { ... } // 你也可以定义专门处理 Entity 的函数 function getInfoMessage(task: Task): string { const isDoneText = task.isDone ? "is done" : "is not done" return `Task '${task.description}' is ${isDoneText}.` }把Task类型用在函数签名里,就相当于把参数类型与数据库实体"绑定"在一起。这种绑定带来两个好处:
- 消除重复:不必手写与 schema 重复的接口定义;
- 变更即报错:当你修改
schema.prisma中的模型时,导入的Task类型随之改变,任何仍按旧结构编码的代码都会抛出类型错误,从而在编译期就暴露过期的数据定义。
Entity 类型在客户端代码中同样可用:
import { Task } from "wasp/entities" export function ExamplePage() { const task: Task = { id: "some-id", description: "Some random task", isDone: false, } return <div>{task.description}</div> }在仓库的官方 starters 模板中也能看到这种用法,例如waspc/data/Cli/starters/basic/src/tasks/queries.ts与waspc/data/Cli/starters/basic/src/tasks/actions.ts都从wasp/entities导入实体类型并配合 Operations 使用——这正是文档所说"在 Operations 中使用 Entity 类型"的典型场景。
让 Entity 生效:wasp db migrate-dev 工作流
定义好 Entity 之后,需要让数据库与模型定义保持同步。Wasp 的标准工作流分四步:
- 在
schema.prisma中创建或更新 Entity; - 运行
wasp db migrate-dev:该命令会对比数据库与schema.prisma中的 Entity 定义,生成迁移脚本并应用到数据库; - 提交
migrations/目录:迁移脚本会自动放入项目的migrations/文件夹,务必将其纳入版本控制,这样团队成员与 CI 环境才能按相同顺序回放数据库结构变更; - 在代码中使用 Entity:实现 Operations(Queries 与 Actions)时,通过 Wasp 的 JavaScript API 访问数据库。
以仓库中的迁移记录为例,examples/kitchen-sink/migrations 下按时间戳命名的目录(如20240110132515_add_session/、20240516082146_add_votes/、20250321140734_add_visibility_enum/)正是wasp db migrate-dev持续产出迁移脚本的痕迹——每个目录对应一次数据模型演进。
在 Operations 中使用 Entity
绝大多数业务场景下,你会通过 Wasp 的 Operations 机制(Query 读、Action 写)与 Entity 交互。在main.wasp(旧版 Wasp 文件)中,需要把 Entity 显式声明给对应的操作。参见 Prisma Schema File 文档 中的示例:
query getTasks { fn: import { getTasks } from "@src/queries", entities: [Task] } job myJob { executor: PgBoss, perform: { fn: import { foo } from "@src/workers/bar" }, entities: [Task], } api fooBar { fn: import { fooBar } from "@src/apis", entities: [Task], httpRoute: (GET, "/foo/bar/:email") }这里getTasks查询、myJob后台任务、fooBarAPI 都声明依赖TaskEntity,Wasp 会据此注入对应的数据库访问权限与类型。在新版 TypeScript Spec 语法(main.wasp.ts)中写法略有不同,但语义一致,见 examples/tutorials/TodoAppTs/main.wasp.ts:
query(getTasks, { entities: ["Task"] }), action(createTask, { entities: ["Task"] }), action(updateTask, { entities: ["Task"] }),Operations 的完整说明见 Operations 总览文档(包含 Queries 与 Actions)。
直接使用 Prisma Client
当标准 Operations 无法满足需求时,你可以在 Wasp 服务端代码中直接使用 Prisma Client与 Entity 交互。官方文档明确限定:Prisma Client 只能在服务端使用,导入方式如下:
import { prisma } from 'wasp/server' prisma.task.create({ description: "Read the Entities doc", isDone: true // almost :) })注意这里的细节:prisma.task.create中的task是 Entity 名称的小写形式,这正是 Prisma Client 按模型自动生成的 CRUD 方法命名规则(更多 CRUD 用法可参考 Prisma Client 官方文档)。
这一导入路径在 Wasp 源码中可以得到印证:生成器模板 waspc/data/Generator/templates/server/src/actions/_action.ts 第 2 行正是import { prisma } from 'wasp/server',说明每个 Action 的生成代码都会依赖该模块。另外,waspc/data/Generator/templates/sdk/wasp/client/vite/plugins/detectServerImports.ts 中通过检查模块名是否以wasp/server开头来识别服务端导入,从源码层面确认了wasp/server是服务端专属模块。
推荐策略:优先使用 Wasp 提供的标准机制(Operations),只有在需要 Wasp 未覆盖的 Prisma 特性时才直接操作 Prisma Client。
Wasp 对 schema.prisma 的特殊要求
Wasp 允许你像在普通 JS/TS 项目中一样使用 Prisma schema 文件,但有三条 Wasp 特有的规则必须遵守(详见 Prisma Schema File 文档):
datasource 块
datasource db { provider = "postgresql" url = env("DATABASE_URL") }provider只能是"postgresql"或"sqlite",因为 Wasp 目前只支持这两种数据库;url必须设置为env("DATABASE_URL"),Wasp 依赖该环境变量完成数据库连接。
仓库中两种 provider 都有真实用例:examples/kitchen-sink/schema.prisma 使用 PostgreSQL,examples/tutorials/TodoAppTs/schema.prisma 使用 SQLite。
generator 块
generator client { provider = "prisma-client-js" }Wasp 要求 schema 中必须存在provider = "prisma-client-js"的 generator 块,否则无法生成客户端代码。如果你需要其他 Prisma 生成器(如生成文档或自定义代码),可以额外添加。
model 块
只要符合 Prisma Schema 语法,你可以按任意方式定义模型。目前 Wasp 尚未完全支持 schema 文件中的///三斜杠注释语法,如需该功能可关注上游进展。
Prisma preview features
Prisma 的某些新特性仍处于预览阶段,需要在 generator 块中通过previewFeatures显式启用。一个典型场景是 PostgreSQL 扩展支持,例如启用pgvector进行向量检索:
datasource db { provider = "postgresql" url = env("DATABASE_URL") extensions = [pgvector(map: "vector")] } generator client { provider = "prisma-client-js" previewFeatures = ["postgresqlExtensions"] }仓库中的 examples/ask-the-documents/schema.prisma 就是这一配置的完整落地示例——它用Unsupported("vector(1536)")类型声明了 AI 文档问答场景中的向量字段:
model Document { id String @id @default(uuid()) title String url String @unique content String embedding Unsupported("vector(1536)") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }这展示了 Wasp Entity 并不局限于基础数据类型:借助 Prisma 的 preview features 与Unsupported类型,你可以建模向量、JSON 等高级字段,满足 AI 时代应用的存储需求。
实战要点回顾
- Entity = 数据库表:在根目录
schema.prisma中用 Prisma model 声明,Wasp 自动识别为 Entity; - 类型安全贯穿前后端:
wasp/entities导出的实体类型在客户端与服务端一致可用,schema 变更会即时反映为类型错误; - 迁移流程是刚需:每次改动模型都要执行
wasp db migrate-dev,并把生成的migrations/目录提交到版本控制; - 两条数据访问路径:常规业务走 Operations(
entities: [Task]声明),高级需求在服务端import { prisma } from 'wasp/server'直连数据库; - 三条 schema 红线:
provider仅限 PostgreSQL/SQLite、url必须是env("DATABASE_URL")、必须存在prisma-client-jsgenerator。
掌握 Entity 之后,下一步就是学习如何围绕它构建完整的读写逻辑——即 Wasp 的 Operations(Queries 与 Actions),相关文档见 Operations 总览。
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考