Node.js Prisma ORM
上一篇用 mysql2 手写 SQL,表一多拼接语句就容易出错。这篇介绍 Prisma,它把数据库表变成 JavaScript 对象,查询用方法调用完成,不用再写 SQL 字符串。你还会看到它怎么自动建表、自动生成客户端代码,以及常用的查询方法长什么样。
ORM 是什么
ORM 全称对象关系映射,就是把数据库的表映射成编程语言里的对象。原来写 SQL
await pool.execute("SELECT * FROM users WHERE id = ?", [id]);
换成 ORM 的写法
await prisma.user.findUnique({ where: { id } });
两者效果一样,后者不用拼字符串,字段名写错了 IDE 直接标红。Prisma 是目前 Node.js 生态最流行的 ORM,下面看它怎么用。
Prisma 的优势
| 能力 | 说明 |
|---|---|
| schema 声明模型 | 用一个文件定义全部表结构 |
| 自动迁移 | 改模型后一条命令同步数据库 |
| 类型安全 | 查询写错字段编译期就报错 |
| 查询 API | 方法式调用,直观好记 |
安装与初始化
npm install prisma -D
npm install @prisma/client
prisma 是开发工具,负责迁移和生成代码,装到 devDependencies。@prisma/client 是运行时用的查询客户端,装到 dependencies。
npx prisma init
执行后生成 prisma/schema.prisma 和 .env 两个文件。schema 文件里写数据模型,.env 里放数据库连接串。
配置数据源
打开 prisma/schema.prisma,把 provider 改成 mysql
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
连接串仍然从环境变量读,环境变量的玩法参考 Node.js 环境变量与配置。.env 里写上
DATABASE_URL="mysql://root:123456@127.0.0.1:3306/blog"
定义模型
在 schema.prisma 里加一个 User 模型
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}
| 注解 | 作用 |
|---|---|
| @id | 主键 |
| @default(autoincrement()) | 自增,对应 MySQL 的 AUTO_INCREMENT |
| @unique | 唯一约束,email 重复会报错 |
| @default(now()) | 插入时自动填当前时间 |
迁移
模型写好后执行迁移
npx prisma migrate dev --name init
这条命令会创建数据库表、生成迁移文件,并自动执行 prisma generate 更新客户端。之后每次改模型,再跑一次 npx prisma migrate dev --name 新名字 就行。
迁移文件存在 prisma/migrations 目录,每个迁移是一个带时间戳的文件夹,里面是生成的 SQL 文件,可以放进 git 版本管理。换电脑或部署时跑 npx prisma migrate deploy,数据库结构就按顺序执行这些文件,和开发环境保持一致。
单独跑 npx prisma generate 的场景是刚 clone 项目或者换了新电脑,生成的客户端代码在 node_modules 里,不会进 git,所以新环境要重新生成一次。
mysql2 与 Prisma 对比
| 操作 | mysql2 写法 | Prisma 写法 |
|---|---|---|
| 插入 | INSERT INTO users (name, email) VALUES (?, ?) |
prisma.user.create({ data: { name, email } }) |
| 查询全部 | SELECT * FROM users |
prisma.user.findMany() |
| 查单条 | SELECT * FROM users WHERE id = ? |
prisma.user.findUnique({ where: { id } }) |
| 更新 | UPDATE users SET name = ? WHERE id = ? |
prisma.user.update({ where: { id }, data: { name } }) |
| 删除 | DELETE FROM users WHERE id = ? |
prisma.user.delete({ where: { id } }) |
每一行都是一一对应的,Prisma 只是把字符串换成了对象,语义完全一样。看表里还缺一个能力,下一篇用 include 处理表之间的关联。
查询 API
创建
const user = await prisma.user.create({
data: { name: "小明", email: "xm@example.com" }
});
console.log(user.id); // 自增 id
对应上一篇的 INSERT INTO users ...,data 对象里的字段会生成 INSERT 语句。
批量插入用 createMany
const result = await prisma.user.createMany({
data: [
{ name: "小刚", email: "gang@example.com" },
{ name: "小丽", email: "li@example.com" }
]
});
console.log(result.count); // 2
查询
// 查全部,findMany
const users = await prisma.user.findMany();
console.log(users); // 数组
// 按 id 查单条,findUnique
const user = await prisma.user.findUnique({
where: { id: 1 }
});
console.log(user); // 对象,查不到是 null
// 带条件查询,findMany 支持 where 和 orderBy
const recent = await prisma.user.findMany({
where: { createdAt: { gte: new Date("2026-01-01") } },
orderBy: { createdAt: "desc" }
});
where 里写的是筛选条件,gte 是大于等于,orderBy 控制排序,语法和 SQL 的 WHERE、ORDER BY 一一对应。
更新
const user = await prisma.user.update({
where: { id: 1 },
data: { name: "小红" }
});
删除
const user = await prisma.user.delete({
where: { id: 1 }
});
每个方法第一个参数是条件对象,where 和 data 写什么,对应 SQL 的 WHERE 和 SET,结构比字符串拼接清晰得多。
实践
用 Prisma 重写用户 CRUD,替换上一篇的 userModel。
// userModel.js
const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
async function list() {
return prisma.user.findMany();
}
async function getById(id) {
return prisma.user.findUnique({ where: { id } });
}
async function create(name, email) {
return prisma.user.create({ data: { name, email } });
}
async function update(id, name) {
return prisma.user.update({ where: { id }, data: { name } });
}
async function remove(id) {
return prisma.user.delete({ where: { id } });
}
module.exports = { list, getById, create, update, remove };
路由层完全不用改,接口对外表现和上一篇一样,只是内部从字符串 SQL 换成了 Prisma 查询。练习一下把上一篇的 test.js 跑起来,结果应该一模一样。
常见坑
- 改了 schema 忘记跑
prisma migrate dev,查询报表不存在。 - 迁移报 ER_DUP_ENTRY,email 撞了 @unique 约束,先查数据再迁移。
- 装完 @prisma/client 没跑 generate,client 不认识新模型。
- 拿 findMany 的返回去调属性,它返回的是数组,单条要用 findUnique。
- 迁移成功后把 .env 的 DATABASE_URL 写错,连接报错先看这个变量。
手写 SQL 和 ORM 都跑通了,下一篇解决表之间怎么关联,见 Node.js 数据库设计与关联。