跳到主要内容

手把手实践入门Prisma

·

概要#

欢迎来到 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 时的各种流程。

  1. 设置 Prisma 和 SQLite 数据库,并使用 Prisma 进行数据建模和数据库变更。
  2. 学习类型安全的 ORM 库 Prisma Client。我们会测试各种查询,从单纯的 CRUD 到关系查询,再到过滤和分页查询。
  3. 学习使用 Prisma Client 实现 REST API。
  4. 学习使用 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

形式#

课程分为两部分:

  1. 主持人演示:主持人将通过会议或直播的形式展示,也会提供回放视频,你可以随时观看和提问。
  2. 自己动手:看完课程请尽量自己动手实践一次,加深记忆。

联系#

提问或加入 Prisma 中国社区微信群可以扫描下方二维码(vx: k961082967)添加主持人微信,麻烦备注 prisma:

prisma community

课程#

一:设置 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())

auto

任务#

在完成每一小节任务后,都在终端执行 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 stashgit checkout rest-apirm -rf prisma/migrationsrm prisma/dev.dbrm -rf node_modulesnpm 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 stashgit checkout graphql-apirm -rf prisma/migrationsrm prisma/dev.dbrm -rf node_modulesnpm 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:4000Apollo 自带的 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 的谢意和致敬吧,预祝它越走越远!