概要
欢迎来到 Prisma workshop!
Prisma 是一个现代化的后端数据层抽象。包含数据库 ORM,GUI 和类 CLI。
2021 年 6 月 29-30 号是全球 Prisma Day,中国区 workshop 由我来主持。
本次研讨会主要面向 Prisma 初学者,我们来一步一步操作实践,入门 Prisma。
这是研讨会对应的文字版文档。点击查看更多。
相关资源:
中文文档: https://www.baasapi.com/blog/prisma
英文文档: https://pris.ly/a-practical-introduction-prisma
GitHub 仓库: https://github.com/nikolasburk/prisma-workshop
Prisma 文档: https://www.prisma.io/
前提条件:
- 确保你的电脑已安装 Nodejs v10.16 以上的版本;
- 电脑已安装 git;
- 推荐使用 VS Code 编辑器来编写代码;
无需提前了解 SQL 和 Prisma。
流程
在本研讨会中,我们将一步一步操作使用 Prisma 时的各种流程。
- 设置 Prisma 和 SQLite 数据库,并使用 Prisma 进行数据建模和数据库变更。
- 学习类型安全的 ORM 库 Prisma Client。我们会测试各种查询,从单纯的 CRUD 到关系查询,再到过滤和分页查询。
- 学习使用 Prisma Client 实现 REST API。
- 学习使用 Prisma Client 实现 GraphQL API。
时间
2021 年 6 月 29 日:
17:00 入场
17:10 设置 Prisma,数据模型和变更
17:30 学习 Prisma Client
18:10 用 Express 实现 REST API
18:40 用 Apollo Server 实现 GraphQL API
形式
课程分为两部分:
- 主持人演示:主持人将通过会议或直播的形式展示,也会提供回放视频,你可以随时观看和提问。
- 自己动手:看完课程请尽量自己动手实践一次,加深记忆。
联系
提问或加入 Prisma 中国社区微信群可以扫描下方二维码(vx: k961082967)添加主持人微信,麻烦备注 prisma:
课程
一:设置 Prisma
目标
本节的目标是设置 Prisma 项目,熟悉 Prisma 的数据建模语言并执行第一次数据库变更。
设置
首先,使用 VS Code 新建一个项目,用 git 克隆 GitHub 仓库,按照 README.md
文件中的说明进行操作。
最近因为网络问题,最好使用以下镜像 clone。
git clone https://gitee.com/baasapi-admin/prisma-workshop.git
cd prisma-workshop
npm install
# 也可以使用cnpm 或 yarn 等自己习惯的包管理器
任务
克隆 repo 并安装 npm 依赖项之后,就可以开始本课的任务了 💪
任务 1:创建第一个 Prisma 模型
Prisma schema(通常为“schema.prisma”)文件是所有 prisma 项目的核心。现在可以看到项目目录中的 Prisma schema 如下所示:
// prisma/schema.prisma
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
此处 VS Code 可能会有提示,对
.prisma
文件扩展安装插件,可以安装一个prisma
插件以格式化。
当前 schema 中有两个设置项。
datasource
指定了数据库连接是 SQLite 数据库,以及数据库地址。
generator
指定了等会要生成的 Prisma Client 为 JavaScript 语言。
除此之外,prisma schema 还要有 prisma models 模型,也就是真实数据库表的映射。在本例中,我们将创建第一个 prisma 模型。
首先用下面这些字段创建一个 User 用户
模型并选择合适的数据类型:
id
:一个自动递增的整数 ID,用于唯一标识数据库中的每个用户name
:用户名称,此字段在数据库中可以为空email
:用户的邮箱地址,此字段在数据库中应该为必需和唯一的
填写如下:
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
}
任务 2:执行第一次数据库变更
当我们设置好第一个模型后,就可以用它来设置数据库表了。这里我们运行以下命令,就会利用 prisma migrate 库执行数据库变更:
npx prisma migrate dev --name init
这里的
--name init
就是给这次变更起个名字,利于查阅和修改。
如果上面的命令执行没出问题的话,prisma
目录中就会多出两个新内容:
一个
migrations
目录,用于跟踪每次数据库变更的信息一个
dev.db
文件,它是我们的 SQLite 数据库文件
任务 3:使用 Prisma Studio 插入数据库数据
好的,我们刚刚使用 Prisma 创建了一个新数据库,其中包含一个名为“User”的表。在进入下一节之前,我们来使用 Prisma Studio插入三条数据库数据。运行以下命令打开 Prisma Studio:
npx prisma studio
打开后,在User
表中创建三条记录并保存到数据库中。
Prisma Studio 是数据库的 GUI,可用于查看和编辑数据库中的数据。
二:了解 Prisma Client
目标
本节的目标是熟悉 Prisma Client 的 API,尝试一下查询数据库。我们将学到 CRUD 查询,关系查询,过滤和分页等。并且我们还要再加一个原先的用户模型有关联关系的新模型。
设置
继续上一节的项目,我们可以看到有一个 script.ts
文件,里面有一个 main
函数,每次执行该脚本时就会调用这个函数。
小提示
尽量手打课程的代码,不要复制粘贴
这样记忆更深。
充分利用自动补全
Prisma Client 会自动生成基于模型的数据库 API,这样我们就可以利用 VS Code 提供的自动补全功能来提升开发体验。
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const result = await prisma. // 当你打出那个 . 时,编辑器就会出现自动补全提示,选择合适的API后按下Tab键就能自动写好了
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.disconnect())
任务
在完成每一小节任务后,都在终端执行 npm run dev
命令来运行脚本查看效果。
任务 1:查询所有用户
开始写代码,第一个用最简单的查询,用 console.log
打印结果查看。
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.findMany();
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
如果你 clone 的是 github 版本,那么这里会报错没有
@types/node
,执行npm i -D @types/node
安装即可。
任务 2:创建一个新用户
因为邮箱字段是必填项,所以我们得填写这个:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.create({
data: {
email: "victor@baasapi.com",
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 3:更新一个已存在的用户
给上一步中的用户更新一个名字:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.update({
where: {
email: "victor@baasapi.com",
},
data: {
name: "Victor",
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 4:添加一个 Post
文章表到数据库中
为了尝试更多数据库操作,我们添加一个新的模型,拥有和其他模型的关联关系。
打开schema.prisma
文件,修改为:
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
- title 是文章标题
- content 是文章内容
- published 是文章是否发布
- author 和 authorId 决定了文章和用户的关系,这里是可选的,就代表文章不一定需要一个作者用户。同时也要在用户模型加入文章的关系,这样才是双向的。
添加完后我们将变更更新到数据库中:
npx prisma migrate dev --name add-post
任务 5:新建一篇文章
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.post.create({
data: {
title: "Hello World",
content: "First",
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 6:将用户和文章关联起来
现在数据库中有几个用户和一篇文章,它们可以通过 authorId
外键连接起来。
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.post.update({
where: { id: 1 },
data: {
author: {
connect: { email: "victor@baasapi.com" },
},
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 7:根据 ID 查询用户
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.findUnique({
where: { email: "victor@baasapi.com" },
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 8:查询时只返回部分列数据
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.findMany({
select: {
id: true,
name: true,
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 9:嵌套查询
将关联的数据一起查询:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.findUnique({
where: { email: "victor@baasapi.com" },
include: { posts: true },
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 10:同时新建用户和文章
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.create({
data: {
name: "Nikolas",
email: "burk@prisma.io",
posts: {
create: {
title: "A practical introduction to Prisma",
content: "Second",
},
},
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 11:过滤查询名字以“V”开头的用户
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.findMany({
where: {
name: {
startsWith: "V",
},
},
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
任务 12:分页查询
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const result = await prisma.user.findMany({
skip: 2,
take: 2,
});
console.log(result);
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
下一步
好的,我们已经初步了解了基本的操作,还有更多功能比如说排序、upsert、原生等。如果想进一步学习,请查看 文档
三:REST API
目标
本节的目标是使用刚才了解到的 Prisma Client 知识和 Express 框架搭建一个 REST API 服务。
设置
我们继续使用上一节的项目,切换一下分支即可,然后在切换后删除数据库文件并重新安装依赖。
git stash
git checkout rest-api
rm -rf prisma/migrations
rm prisma/dev.db
rm -rf node_modules
npm install
可以看到有新的数据模型,和上一节差不多。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
重新创建数据库和表:
npx prisma migrate dev --name init
最后,给数据库中添加一些初始数据,执行脚本prisma/seed.ts
:
npx ts-node .\prisma\seed.ts
任务
好的,现在我们可以看到项目出现了scr
目录和index.ts
文件,里面包含了 Express 服务和一些预设好的 HTTP 路由,接下来,我们就来依次实现每个接口。
另外还有一个
test.http
文件,可以用 VS Code 插件 REST Client 来进行 API 调用测试。
GET /users
查询所有用户:
app.get("/users", async (req, res) => {
const result = await prisma.user.findMany();
res.json(result);
});
POST /signup
新建用户:
app.post(`/signup`, async (req, res) => {
const { name, email } = req.body;
const result = await prisma.user.create({
data: {
name,
email,
},
});
res.json(result);
});
POST /post
新建文章:
app.post(`/post`, async (req, res) => {
const { title, content, authorEmail } = req.body;
const result = await prisma.post.create({
data: {
title,
content,
author: {
connect: {
email: authorEmail,
},
},
},
});
res.json(result);
});
PUT /post/:id/views
文章阅读量加 1:
app.put("/post/:id/views", async (req, res) => {
const { id } = req.params;
const result = await prisma.post.update({
where: {
id: Number(id),
},
data: {
viewCount: {
increment: 1,
},
},
});
res.json(result);
});
PUT /publish/:id
发布文章:
app.put("/publish/:id", async (req, res) => {
const { id } = req.params;
const result = await prisma.post.update({
where: { id: Number(id) },
data: {
published: true,
},
});
res.json(result);
});
GET /user/:id/drafts
查询某个用户的草稿:
app.get("/user/:id/drafts", async (req, res) => {
const { id } = req.params;
const result = await prisma.user
.findUnique({
where: { id: Number(id) },
})
.posts({
where: {
published: false,
},
});
res.json(result);
});
GET /post/:id
查询所有用户:
app.get(`/post/:id`, async (req, res) => {
const { id } = req.params;
const result = await prisma.post.findUnique({
where: { id: Number(id) },
});
res.json(result);
});
GET /feed?searchString=<searchString>&skip=<skip>&take=<take>
获取所有已发布的文章,并根据请求参数控制查询过滤和分页。
app.get("/feed", async (req, res) => {
const { searchString, skip, take } = req.query;
const or = searchString
? {
OR: [
{ title: { contains: searchString as string } },
{ content: { contains: searchString as string } },
],
}
: {};
const result = await prisma.post.findMany({
where: {
published: true,
...or,
},
skip: Number(skip) || undefined,
take: Number(take) || undefined,
});
res.json(result);
});
四:GraphQL API
目标
本节的目标是使用刚才了解到的 Prisma Client 知识和 Apollo Server 框架搭建一个 GraphQL API 服务。
设置
我们继续使用上一节的项目,切换一下分支即可,然后在切换后删除数据库文件并重新安装依赖。
git stash
git checkout graphql-api
rm -rf prisma/migrations
rm prisma/dev.db
rm -rf node_modules
npm install
可以看到数据模型和上一节一样。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
重新创建数据库和表:
npx prisma migrate dev --name init
最后,给数据库中添加一些初始数据,执行脚本prisma/seed.ts
:
npx ts-node .\prisma\seed.ts
任务
好的,项目依然包含scr
目录和index.ts
文件,里面包含了 Apollo Server 服务和一些预设好的配置,接下来,我们就来依次实现每个接口。另外,我们可以打开http://localhost:4000
Apollo 自带的 API 页面,去调式接口。
Query.allUsers: [User!]!
查询所有用户:
allUsers: (_parent, _args, context: Context) => {
return context.prisma.user.findMany()
},
查询语句:
{
allUsers {
id
name
email
posts {
id
title
}
}
}
Query.postById(id: Int!): Post
通过 ID 查询文章:
postById: (_parent, args: { id: number }, context: Context) => {
return context.prisma.post.findUnique({
where: { id: args.id }
})
},
查询语句:
{
postById(id: 1) {
id
title
content
published
viewCount
author {
id
name
email
}
}
}
Query.feed(searchString: String, skip: Int, take: Int): [Post!]!
获取所有已发布的文章,并根据请求参数控制查询过滤和分页:
feed: (
_parent,
args: {
searchString: string | undefined;
skip: number | undefined;
take: number | undefined;
},
context: Context
) => {
const or = args.searchString
? {
OR: [
{ title: { contains: args.searchString as string } },
{ content: { contains: args.searchString as string } },
],
}
: {};
return context.prisma.post.findMany({
where: {
published: true,
...or,
},
skip: Number(args.skip) || undefined,
take: Number(args.take) || undefined,
});
},
查询语句:
{
feed {
id
title
content
published
viewCount
author {
id
name
email
}
}
}
Query.draftsByUser(id: Int!): [Post]
查询某个用户的所有未发布的文章:
draftsByUser: (_parent, args: { id: number }, context: Context) => {
return context.prisma.user.findUnique({
where: { id: args.id }
}).posts({
where: {
published: false
}
})
},
查询语句:
{
draftsByUser(id: 3) {
id
title
content
published
viewCount
author {
id
name
email
}
}
}
Mutation.signupUser(name: String, email: String!): User!
新建用户:
signupUser: (
_parent,
args: { name: string | undefined; email: string },
context: Context
) => {
return context.prisma.user.create({
data: {
name: args.name,
email: args.email
}
})
},
GraphQL 语句:
mutation {
signupUser(name: "Nikolas", email: "burk@prisma.io") {
id
posts {
id
}
}
}
Mutation.createDraft(title: String!, content: String, authorEmail: String): Post
新建文章:
createDraft: (
_parent,
args: { title: string; content: string | undefined; authorEmail: string },
context: Context
) => {
return context.prisma.post.create({
data: {
title: args.title,
content: args.content,
author: {
connect: {
email: args.authorEmail
}
}
}
})
},
查询语句:
mutation {
createDraft(title: "Hello World", authorEmail: "burk@prisma.io") {
id
published
viewCount
author {
id
email
name
}
}
}
Mutation.incrementPostViewCount(id: Int!): Post
文章阅读量加 1:
incrementPostViewCount: (
_parent,
args: { id: number },
context: Context
) => {
return context.prisma.post.update({
where: { id: args.id },
data: {
viewCount: {
increment: 1
}
}
})
},
查询语句:
mutation {
incrementPostViewCount(id: 1) {
id
viewCount
}
}
Mutation.deletePost(id: Int!): Post
删除文章:
deletePost: (_parent, args: { id: number }, context: Context) => {
return context.prisma.post.delete({
where: { id: args.id }
})
},
GraphQL 语句:
mutation {
deletePost(id: 1) {
id
}
}
User.posts: [Post!]!
查询某用户的文章:
User: {
posts: (parent, _args, context: Context) => {
return context.prisma.user.findUnique({
where: { id: parent.id }
}).posts()
},
},
Post.author: User
查询文章的作者:
Post: {
author: (parent, _args, context: Context) => {
return context.prisma.post.findUnique({
where: { id: parent.id }
}).author()
},
},
好的,完成所有接口后,我们启动服务,前往 http://localhost:4000
的 playground 进行测试。
恭喜,我们已经完成了这次实践,都是一些比较基础的功能,大家有问题的可以随时提问。如果想要学习更多,可以前往 Prisma 文档地址( prisma.io)阅读。
Prisma 是一个非常优秀的数据管理抽象,能大大方便后端开发。
我本人在几年前接触 prisma,觉得它的价值应该被更多人知道,所以就翻译了文档,建立了社区,方便国内用户学习。虽然中途由于产品定位变更,prisma 经历了一次较大的改动,流失了很多用户,但是经过这次 workshop,我们可以看到新的 prisma 有了更好的体验,超越了第一代。
Prisma 的很多特性和思想也影响了我后来的创业方向和产品设计。当时 Prisma 1 可以直接将 GraphQL 和数据库对应起来,很多人就在群里问,能不能把 prisma 自动生成的接口直接暴露出来给前端用?大部分的项目都是简单的 CRUD,只要设计好数据模型就能利用自动生成的代码直接使用多方便。
当然因为安全问题这是不可以的,但是我也看到了很多朋友的需求就是方便、快速、简单,那么当时我就想,如果把安全措施加上去会如何?
这个简单的想法种子在当时种下,后面我在做企业 IT 咨询时经历了很多项目,也看了中台,低代码等等流行的趋势,一个做 BaaS 平台的想法就逐渐成熟了起来。
相比很多人的需求和企业场景,prisma 不可避免有很多不足之处,比如说不适用大规模分布式应用和大量数据场景、依然需要后端开发人员去配置学习部署等。所以我和我的团队花了很多时间都在解决这些需求,最终发布了清林云 BaaS。
用户在网页端设计数据模型,设计 API 逻辑,配置安全措施,就可以直接开发出一个应用后端出来。另外也可以将这些组合为一个应用,发布到应用市场中共享,让所有人都可以直接使用,也可以直接使用别人已经做好的、符合自己需求的应用。这样,使用 BaaS 就不再需要开发后端,这不就是很多人的需求吗?
我的第二次创业受益于 Prisma 优秀的理念,所以我依然会抽时间维护社区,这也是我对 Prisma 的谢意和致敬吧,预祝它越走越远!