六维教程

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 }
});

每个方法第一个参数是条件对象,wheredata 写什么,对应 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 数据库设计与关联

上一篇
Node.js MySQL 数据库
下一篇
Node.js 数据库设计与关联