技术篇:数据库-Prisma
Next.js实战教程 - 系列文章
学习目标:为应用接入数据库存储
Prisma 简介 #
Prisma 是一个现代化的 ORM(对象关系映射)工具,支持 TypeScript 和 JavaScript,旨在简化数据库操作,提高开发效率。支持多种数据库,包括 PostgreSQL、MySQL、SQLite 和 MongoDB,同时 Prisma 很容易与 Next.js、NestJS 等框架集成,简化全栈开发。本文将详细介绍如何在 Next.js 项目中接入 Prisma,并使用其进行数据库操作。
项目初始化 #
npx create-next-app@latest
✔ What is your project named? … example-prisma
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
安装和配置 Prisma #
安装 Prisma CLI
yarn add prisma --dev
- Prisma CLI 是用于初始化 Prisma 设置、生成 Prisma 客户端、以及执行数据库迁移的命令行工具。
安装 Prisma Client
yarn add @prisma/client
- Prisma Client 是 Prisma 提供的一个自动生成的、类型安全的数据库查询工具。它的主要作用是帮助开发者与数据库进行交互,而不需要编写原始 SQL 语句。
安装 Prisma CLI 之后,可以通过以下命令初始化 Prisma 配置
yarn prisma init
此命令将在你的项目根目录下创建一个 prisma/
目录,里面包含一个 schema.prisma
文件,以及一个 .env
文件。schema.prisma
是你定义数据库模型的地方,而 .env
文件则用于存储数据库连接信息。
配置数据库连接 #
在 prisma/schema.prisma
文件中,你需要设置数据库链接,以 MySQL 为例配置如下所示:
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
在 .env
文件中配置你的数据库连接:
DATABASE_URL="mysql://root:123456@localhost:3306/example"
你可以根据使用的数据库类型更改此连接。Prisma 支持多种数据库,每种数据库的连接字符串格式可能有所不同。
定义数据模型 #
在 prisma/schema.prisma
文件中,你可以定义数据库模型。例如,定义一个简单的用户模型:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
这个模型定义了一个 User
表,其中包含一个自增的 id
、唯一的 email
、可选的 name
、以及自动记录的 createdAt
和 updatedAt
时间戳。
生成迁移并应用到数据库(可选) #
定义好模型后,你需要将模型同步到数据库。这可以通过生成迁移文件并应用它们来实现:
yarn prisma migrate dev --name init
此命令将生成一个迁移文件,并将其应用到数据库。
--name
用来指定这次迁移的名称,在这个例子中是init
。迁移名称会被用来创建迁移文件夹的名称,这些文件夹位于prisma/migrations/
目录下。- 例如,如果使用
--name init
,生成的迁移文件夹可能会被命名为20240923030702_init
,其中20240923030702
是一个时间戳,init
是你指定的迁移名称。
执行完成之后,数据库中就会创建完成如上图所示的表。
生成的迁移文件夹
数据库中创建的表结构
注意:
在生产环境中,yarn prisma migrate dev
不适合直接执行。这个命令通常用于开发阶段,它会生成迁移文件并应用到数据库。在生产环境下,应该使用 yarn prisma migrate deploy
来应用已经生成的迁移文件,而不是使用 yarn prisma migrate dev
。yarn prisma migrate dev
在 prisma/migrations
目录中创建迁移文件。yarn prisma migrate deploy
会检测 prisma/migrations
目录中的迁移文件,并依次将这些迁移应用到生产数据库中,确保生产数据库结构与 Prisma Schema 文件一致。可以使用 yarn prisma migrate status
命令来查看当前数据库的迁移状态,确保迁移是否已经成功应用或需要手动处理。
这一步的目录是数据库表与 Prisma Schema 一致,因此我们也可以不用 migrate,而是手动建表,只需要确保 Prisma 的 schema.prisma
文件中的模型与手动创建的数据库表结构保持一致。
此外,如果你有一个现有的数据库,而不想手动编写 schema.prisma
文件,你可以使用 Prisma 的 db pull
命令,它会自动从数据库中导入表结构,并更新 schema.prisma
文件。
执行以下命令来同步数据库结构:
yarn prisma db pull
这会根据数据库中的表生成相应的 Prisma 模型,并将其写入到 schema.prisma
文件中。
生成 Prisma Client #
Prisma Client 是一个自动生成的、类型安全的数据库客户端,用于与数据库交互。生成客户端可以通过以下命令完成:
yarn prisma generate
yarn prisma generate
命令会根据 schema.prisma
文件中的定义自动生成一个类型安全的数据库客户端,通常位于 node_modules/.prisma/client
中。这个客户端提供了各种数据库操作的接口,用于执行查询、创建、更新和删除操作。每次你对 schema.prisma
文件进行修改(例如新增或修改模型),需要运行 yarn prisma generate
来重新生成 Prisma Client,以确保客户端代码与最新的数据库模型保持同步。
在 Next.js 中使用 Prisma Client #
通过 Prisma Client,开发者可以通过简单直观的 API 来执行 CRUD(创建、读取、更新、删除)操作,而不需要编写复杂的 SQL 语句。例如:
Create - 创建一个新的用户 #
const item = await prisma.user.create({
data: {
email: req.email,
name: req.name,
},
});
- 说明:
email
和name
是输入的用户数据。createdAt
使用的是默认值,updatedAt
会在记录创建或更新时自动更新。
Delete - 删除指定用户 #
const item = await prisma.user.delete({
where: {
id: parseInt(req.id),
},
});
- 说明:此操作会根据指定的
id
删除用户,并返回被删除用户的记录。
Update - 更新指定用户信息 #
const item = await prisma.user.update({
where: {
id: parseInt(req.id),
},
data: {
name: req.name,
},
});
- 说明:此操作根据
id
查找用户并更新其name
。updatedAt
字段将自动更新为当前时间。
Read - 查找用户 #
查找单个用户 #
const user = await prisma.user.findUnique({
where: {
id: 1,
},
});
- 说明:
findUnique
用于查找单条记录,可以根据id
或其他唯一字段(如email
)。
查找多个用户 #
const items = await prisma.user.findMany();
- 说明:
findMany
用于查找多条记录,支持多种查询条件,例如模糊查询、范围查询等。
常用查询方法: #
findUnique
:查询单条记录。findMany
:查询多条记录。create
:创建一条新记录。update
:更新记录。delete
:删除记录。upsert
:更新或创建记录。aggregate
:执行聚合查询,如count
、avg
、sum
。
设置 Prisma Client #
在 Next.js 中,Prisma Client 通常在服务器端代码中使用。首先,在项目中创建一个文件用于实例化 Prisma Client:
// lib/prisma.js
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global;
const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
export default prisma;
此文件确保在开发环境中只实例化一次 Prisma Client,以避免热重载导致的多次实例化问题。
在 API 路由中使用 Prisma #
我们把之前代码里的增、删、改、查放到 app/api/user/route.js
中,完整代码如下:
import prisma from "../../lib/prisma";
export async function POST(request) {
const req = await request.json();
const item = await prisma.user.create({
data: {
email: req.email,
name: req.name,
},
});
return new Response(
JSON.stringify({
id: item.id,
email: item.email,
name: item.name,
createdAt: item.createdAt,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
export async function DELETE(request) {
const req = await request.json();
const item = await prisma.user.delete({
where: {
id: parseInt(req.id),
},
});
return new Response(
JSON.stringify({
id: item.id,
message: `User with ID ${item.id} deleted successfully.`,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
export async function PUT(request) {
const req = await request.json();
const item = await prisma.user.update({
where: {
id: parseInt(req.id),
},
data: {
name: req.name,
},
});
return new Response(
JSON.stringify({
id: item.id,
email: item.email,
name: item.name,
updatedAt: item.updatedAt,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
export async function GET() {
const items = await prisma.user.findMany();
return new Response(
JSON.stringify(
items.map((item) => ({
id: item.id,
email: item.email,
name: item.name,
createdAt: item.createdAt,
}))
),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
然后,再写一个简单的页面 app/page.js
export default function Home() {
// 创建用户
const createUser = async () => {
const res = await fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "example@dram.ai", name: "example" }),
});
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json();
console.log("Response Data:", data);
};
// 删除用户
const deleteUser = async () => {
const res = await fetch(`/api/user`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1 }),
});
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json();
console.log("Response Data:", data);
};
// 更新用户
const updateUser = async () => {
const res = await fetch("/api/user", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: "1", name: "example2" }),
});
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json();
console.log("Response Data:", data);
};
// 更新用户
const getUser = async () => {
const res = await fetch("/api/user", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json();
console.log("Response Data:", data);
};
return (
<div className="p-10 flex flex-col justify-start items-start">
<button onClick={createUser}>Create User</button>
<button onClick={updateUser}>Update User</button>
<button onClick={deleteUser}>Delete User</button>
<button onClick={getUser}>Get User</button>
</div>
);
}
最后分别点击对应的按钮,可以看到数据库增、删、改、查操作都被正确执行。
总结 #
通过将 Prisma 集成到 Next.js 中,可以简化数据库操作,使得数据管理更加直观高效。