我们经常用 restful 的接口来开发业务。

比如 GET 请求 /students 查询所有学生,/students/1 查询 id 为 1 的学生

发送 POST、PUT、DETETE 请求分别代表增删改。

其实也可以用 GraphQL 的方式来写接口:

查询: 图片

新增: 图片

图片

增删改查都在一个接口里搞定,并且想要什么数据由前端自己取。

今天我们就用 Nest + GrahQL 做一个 TodoList 的增删改查。

数据存在 mysql 里,用 Prisma 作为 ORM 框架。

<span></span><code>npm&nbsp;install&nbsp;-g&nbsp;@nestjs/cli<br><br>nest&nbsp;new&nbsp;graphql-todolist<br></code>

图片

创建个项目,然后我们首先来实现 restful 接口的增删改查。

用 docker 把 mysql 跑起来:

从 docker 官网下载 docker desktop,这个是 docker 的桌面端:

图片

跑起来后,搜索 mysql 镜像(这步需要科学上网),点击 run:

图片

输入容器名、端口映射、以及挂载的数据卷,还要指定一个环境变量:

图片

端口映射就是把宿主机的 3306 端口映射到容器里的 3306 端口,这样就可以在宿主机访问了。

数据卷挂载就是把宿主机的某个目录映射到容器里的 /var/lib/mysql 目录,这样数据是保存在本地的,不会丢失。

而 MYSQL_ROOT_PASSWORD 的密码则是 mysql 连接时候的密码。

图片

跑起来后,我们用 GUI 客户端连上,这里我们用的是 mysql workbench,这是 mysql 官方提供的免费客户端:

图片

连接上之后,点击创建 database:

图片

指定名字、字符集为 utf8mb4,然后点击右下角的 apply。

创建成功之后在左侧就可以看到这个 database 了:

图片

现在还没有表。

我们在 Nest 里用 Prisma 连接 mysql。

进入项目,安装 prisma

<span></span><code>npm&nbsp;install&nbsp;prisma&nbsp;--save-dev<br></code>

执行 prisma init 创建 schema 文件:

<span></span><code>npx&nbsp;prisma&nbsp;init<br></code>

图片

生成了 schema 文件(用来定义 model 的),和 .env 文件:

图片

改下 .env 的配置:

<span></span><code>DATABASE_URL="mysql://root:你的密码@localhost:3306/todolist"<br></code>

并且修改下 schema 里的 datasource 部分:

<span></span><code>datasource&nbsp;db&nbsp;{<br>&nbsp;&nbsp;provider&nbsp;=&nbsp;<span>"mysql"</span><br>&nbsp;&nbsp;url&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;env(<span>"DATABASE_URL"</span>)<br>}<br></code>

然后创建 model:

图片

<span></span><code>generator&nbsp;client&nbsp;{<br>&nbsp;&nbsp;provider&nbsp;=&nbsp;<span>"prisma-client-js"</span><br>}<br><br>datasource&nbsp;db&nbsp;{<br>&nbsp;&nbsp;provider&nbsp;=&nbsp;<span>"mysql"</span><br>&nbsp;&nbsp;url&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;env(<span>"DATABASE_URL"</span>)<br>}<br><br>model&nbsp;TodoItem&nbsp;{<br>&nbsp;&nbsp;id&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Int&nbsp;&nbsp;&nbsp;&nbsp;@id&nbsp;@<span>default</span>(autoincrement())<br>&nbsp;&nbsp;content&nbsp;&nbsp;&nbsp;&nbsp;<span>String</span>&nbsp;&nbsp;@db.VarChar(<span>50</span>)<br>&nbsp;&nbsp;createTime&nbsp;DateTime&nbsp;@<span>default</span>(now())<br>&nbsp;&nbsp;updateTime&nbsp;DateTime&nbsp;@updatedAt<br>}<br></code>

id 自增,content 是长度为 50 的字符串,还有创建时间 createTime、更新时间 updateTime。

执行 prisma migrate dev,它会根据定义的 model 去创建表:

<span></span><code>npx&nbsp;prisma&nbsp;migrate&nbsp;dev&nbsp;--name&nbsp;init<br></code>

图片

它会生成 sql 文件,里面是这次执行的 sql。

然后还会生成 client 代码,用来连接数据库操作这个表。

可以看到,这次执行的 sql 就是 create table 建表语句:

图片

这时候数据库就就有这个表了:

图片

接下来我们就可以在代码里做 CRUD 了。

生成一个 service:

<span></span><code>nest&nbsp;g&nbsp;service&nbsp;prisma&nbsp;--flat&nbsp;--no-spec<br></code>

图片

改下生成的 PrismaService,继承 PrismaClient,这样它就有 crud 的 api 了:

<span></span><code><span>import</span>&nbsp;{&nbsp;Injectable,&nbsp;OnModuleInit&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/common'</span>;<br><span>import</span>&nbsp;{&nbsp;PrismaClient&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@prisma/client'</span>;<br><br>@Injectable()<br><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>PrismaService</span>&nbsp;<span>extends</span>&nbsp;<span>PrismaClient</span>&nbsp;<span>implements</span>&nbsp;<span>OnModuleInit</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>constructor</span>()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>super</span>({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>log</span>:&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>emit</span>:&nbsp;<span>'stdout'</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>level</span>:&nbsp;<span>'query'</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>async</span>&nbsp;onModuleInit()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>await</span>&nbsp;<span>this</span>.$connect();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code>

在 constructor 里设置 PrismaClient 的 log 参数,也就是打印 sql 到控制台。

在 onModuleInit 的生命周期方法里调用 $connect 来连接数据库。

然后在 AppService 里注入 PrismaService,实现 CRUD:

<span></span><code><span>import</span>&nbsp;{&nbsp;Inject,&nbsp;Injectable&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/common'</span>;<br><span>import</span>&nbsp;{&nbsp;PrismaService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./prisma.service'</span>;<br><span>import</span>&nbsp;{&nbsp;CreateTodoList&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist-create.dto'</span>;<br><span>import</span>&nbsp;{&nbsp;UpdateTodoList&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist-update.dto'</span>;<br><br>@Injectable()<br><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>AppService</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;getHello():&nbsp;string&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>'Hello&nbsp;World!'</span>;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;@Inject(PrismaService)<br>&nbsp;&nbsp;private&nbsp;prismaService:&nbsp;PrismaService;<br><br>&nbsp;&nbsp;<span>async</span>&nbsp;query()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.findMany({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>select</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>createTime</span>:&nbsp;<span>true</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;<span>async</span>&nbsp;create(todoItem:&nbsp;CreateTodoList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.create({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>data</span>:&nbsp;todoItem,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>select</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>createTime</span>:&nbsp;<span>true</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;<span>async</span>&nbsp;update(todoItem:&nbsp;UpdateTodoList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.update({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>where</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;todoItem.id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>data</span>:&nbsp;todoItem,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>select</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>createTime</span>:&nbsp;<span>true</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;<span>async</span>&nbsp;remove(id:&nbsp;number)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.delete({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>where</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;}<br>}<br></code>

@Inject 注入 PrismaService,用它来做 CRUD,where 是条件、data 是数据,select 是回显的字段:

图片

然后创建用到的两个 dto 的 class

todolist-create.dto.ts

<span></span><code><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>CreateTodoList</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;string;<br>}<br></code>

todolist-update.dto.ts

<span></span><code><span>export</span>&nbsp;class&nbsp;UpdateTodoList&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;id:&nbsp;number;<br>&nbsp;&nbsp;&nbsp;&nbsp;content:&nbsp;string;<br>}<br></code>

在 AppController 里引入下,添加几个路由:

<span></span><code><span>import</span>&nbsp;{&nbsp;Body,&nbsp;Controller,&nbsp;Delete,&nbsp;Get,&nbsp;Post,&nbsp;Query&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/common'</span>;<br><span>import</span>&nbsp;{&nbsp;AppService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./app.service'</span>;<br><span>import</span>&nbsp;{&nbsp;CreateTodoList&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist-create.dto'</span>;<br><span>import</span>&nbsp;{&nbsp;UpdateTodoList&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist-update.dto'</span>;<br><br>@Controller()<br><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>AppController</span>&nbsp;</span>{<br>&nbsp;&nbsp;<span>constructor</span>(private&nbsp;readonly&nbsp;appService:&nbsp;AppService)&nbsp;{}<br><br>&nbsp;&nbsp;@Get()<br>&nbsp;&nbsp;getHello():&nbsp;string&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.appService.getHello();<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;@Post(<span>'create'</span>)<br>&nbsp;&nbsp;<span>async</span>&nbsp;create(@Body()&nbsp;todoItem:&nbsp;CreateTodoList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.appService.create(todoItem);<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;@Post(<span>'update'</span>)<br>&nbsp;&nbsp;<span>async</span>&nbsp;update(@Body()&nbsp;todoItem:&nbsp;UpdateTodoList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.appService.update(todoItem);<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;@Get(<span>'delete'</span>)<br>&nbsp;&nbsp;<span>async</span>&nbsp;<span>delete</span>(@Query(<span>'id'</span>)&nbsp;id:&nbsp;number)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.appService.remove(+id);<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;@Get(<span>'list'</span>)<br>&nbsp;&nbsp;<span>async</span>&nbsp;list()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.appService.query();<br>&nbsp;&nbsp;}<br><br>}<br></code>

添加增删改查 4 个路由,post 请求用 @Body() 注入请求体,@Query 拿路径中的参数:

图片

把服务跑起来试一下:

<span></span><code>npm&nbsp;run&nbsp;start:dev<br></code>

图片

首先是 list,现在没有数据:

图片

然后添加一个:

图片

服务端打印了 insert into 的 sql:

图片

数据库也有了这条记录:

图片

再加一个:

图片

然后查一下:

图片

接下来试下修改、删除:

图片

图片

再查一下:

图片

没啥问题。

这样,todolist 的 restful 版接口就完成了。

接下来实现 graphql 版本:

安装用到的包:

<span></span><code>npm&nbsp;i&nbsp;@nestjs/graphql&nbsp;@nestjs/apollo&nbsp;@apollo/server&nbsp;graphql<br></code>

然后在 AppModule 里引入下:

<span></span><code><span>import</span>&nbsp;{&nbsp;Module&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/common'</span>;<br><span>import</span>&nbsp;{&nbsp;AppController&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./app.controller'</span>;<br><span>import</span>&nbsp;{&nbsp;AppService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./app.service'</span>;<br><span>import</span>&nbsp;{&nbsp;PrismaService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./prisma.service'</span>;<br><span>import</span>&nbsp;{&nbsp;GraphQLModule&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/graphql'</span>;<br><span>import</span>&nbsp;{&nbsp;ApolloDriver&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/apollo'</span>;<br><br>@Module({<br>&nbsp;&nbsp;<span>imports</span>:&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;GraphQLModule.forRoot({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>driver</span>:&nbsp;ApolloDriver,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>typePaths</span>:&nbsp;[<span>'./**/*.graphql'</span>],<br>&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;],<br>&nbsp;&nbsp;<span>controllers</span>:&nbsp;[AppController],<br>&nbsp;&nbsp;<span>providers</span>:&nbsp;[AppService,&nbsp;PrismaService],<br>})<br><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>AppModule</span>&nbsp;</span>{}<br></code>

typePaths 就是 schema 文件的路径:

图片

添加一个 todolist.graphql

<span></span><code>type TodoItem {<br>    id: Int<br>    content: String<br>}<br><br>input CreateTodoItemInput {<br>  content: String<br>}<br><br>input UpdateTodoItemInput {<br>  id: Int!<br>  content: String<br>}<br><br>type Query {<br>  todolist: [TodoItem]!<br>  queryById(id: Int!): TodoItem<br>}<br><br><br>type Mutation {<br>  createTodoItem(todoItem: CreateTodoItemInput!): TodoItem!<br>  updateTodoItem(todoItem: UpdateTodoItemInput!): TodoItem!<br>  removeTodoItem(id: Int!): Int<br>}<br></code>

语法比较容易看懂,就是定义数据的结构。

在 Query 下定义查询的接口,在 Mutation 下定义增删改的接口。

然后实现 resolver,也就是这些接口的实现:

<span></span><code>nest&nbsp;g&nbsp;resolver&nbsp;todolist&nbsp;--no-spec&nbsp;--flat<br></code>

图片

<span></span><code><span>import</span>&nbsp;{&nbsp;Args,&nbsp;Mutation,&nbsp;Query,&nbsp;Resolver&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/graphql'</span>;<br><span>import</span>&nbsp;{&nbsp;PrismaService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./prisma.service'</span>;<br><span>import</span>&nbsp;{&nbsp;Inject&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/common'</span>;<br><span>import</span>&nbsp;{&nbsp;CreateTodoList&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist-create.dto'</span>;<br><span>import</span>&nbsp;{&nbsp;UpdateTodoList&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist-update.dto'</span>;<br><br>@Resolver()<br><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>TodolistResolver</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;@Inject(PrismaService)<br>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;prismaService:&nbsp;PrismaService;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;@Query(<span>"todolist"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>async</span>&nbsp;todolist()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.findMany();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;@Query(<span>"queryById"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>async</span>&nbsp;queryById(@Args(<span>'id'</span>)&nbsp;id)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.findUnique({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>where</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;@Mutation(<span>"createTodoItem"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>async</span>&nbsp;createTodoItem(@Args(<span>"todoItem"</span>)&nbsp;todoItem:&nbsp;CreateTodoList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.create({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>data</span>:&nbsp;todoItem,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>select</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>createTime</span>:&nbsp;<span>true</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;@Mutation(<span>"updateTodoItem"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>async</span>&nbsp;updateTodoItem(@Args(<span>'todoItem'</span>)&nbsp;todoItem:&nbsp;UpdateTodoList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;<span>this</span>.prismaService.todoItem.update({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>where</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;todoItem.id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>data</span>:&nbsp;todoItem,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>select</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>id</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;<span>true</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>createTime</span>:&nbsp;<span>true</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;@Mutation(<span>"removeTodoItem"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>async</span>&nbsp;removeTodoItem(@Args(<span>'id'</span>)&nbsp;id:&nbsp;number)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>await</span>&nbsp;<span>this</span>.prismaService.todoItem.delete({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>where</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return</span>&nbsp;id;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code>

用 @Resolver 声明 resolver,用 @Query 声明查询接口,@Mutation 声明增删改接口,@Args 取传入的参数。

具体增删改查的实现和之前一样。

浏览器访问 http://localhost:3000/graphql 就是 playground,可以在这里查询:

图片

左边输入查询语法,右边是执行后返回的结果。

当然,对新手来说这个 playground 不够友好,没有提示。

我们换一个:

图片

<span></span><code><span>import</span>&nbsp;{&nbsp;Module&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/common'</span>;<br><span>import</span>&nbsp;{&nbsp;AppController&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./app.controller'</span>;<br><span>import</span>&nbsp;{&nbsp;AppService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./app.service'</span>;<br><span>import</span>&nbsp;{&nbsp;PrismaService&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./prisma.service'</span>;<br><span>import</span>&nbsp;{&nbsp;GraphQLModule&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/graphql'</span>;<br><span>import</span>&nbsp;{&nbsp;ApolloDriver&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@nestjs/apollo'</span>;<br><span>import</span>&nbsp;{&nbsp;TodolistResolver&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'./todolist.resolver'</span>;<br><span>import</span>&nbsp;{&nbsp;ApolloServerPluginLandingPageLocalDefault&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@apollo/server/plugin/landingPage/default'</span>;<br><br>@Module({<br>&nbsp;&nbsp;<span>imports</span>:&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;GraphQLModule.forRoot({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>driver</span>:&nbsp;ApolloDriver,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>typePaths</span>:&nbsp;[<span>'./**/*.graphql'</span>],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>playground</span>:&nbsp;<span>false</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>plugins</span>:&nbsp;[ApolloServerPluginLandingPageLocalDefault()],<br>&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;],<br>&nbsp;&nbsp;<span>controllers</span>:&nbsp;[AppController],<br>&nbsp;&nbsp;<span>providers</span>:&nbsp;[AppService,&nbsp;PrismaService,&nbsp;TodolistResolver],<br>})<br><span>export</span>&nbsp;<span><span>class</span>&nbsp;<span>AppModule</span>&nbsp;</span>{}<br></code>

图片

试一下新增:

图片

查询:

图片

修改:

图片

单个查询:

图片

删除:

图片

查询:

图片

基于 GraphQL 的增删改查都成功了!

然后在 react 项目里调用下。

<span></span><code>npx&nbsp;create-vite<br></code>

图片

进入项目,安装 @apollo/client

<span></span><code>npm&nbsp;install<br><br>npm&nbsp;install&nbsp;@apollo/client<br></code>

改下 main.tsx

<span></span><code><span>import</span>&nbsp;*&nbsp;<span>as</span>&nbsp;ReactDOM&nbsp;<span>from</span>&nbsp;<span>'react-dom/client'</span>;<br><span>import</span>&nbsp;{&nbsp;ApolloClient,&nbsp;InMemoryCache,&nbsp;ApolloProvider&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@apollo/client'</span>;<br><span>import</span>&nbsp;App&nbsp;<span>from</span>&nbsp;<span>'./App'</span>;<br><br><span>const</span>&nbsp;client&nbsp;=&nbsp;<span>new</span>&nbsp;ApolloClient({<br>&nbsp;&nbsp;<span>uri</span>:&nbsp;<span>'http://localhost:3000/graphql'</span>,<br>&nbsp;&nbsp;<span>cache</span>:&nbsp;<span>new</span>&nbsp;InMemoryCache(),<br>});<br><br><span>const</span>&nbsp;root&nbsp;=&nbsp;ReactDOM.createRoot(<span>document</span>.getElementById(<span>'root'</span>)!);<br><br>root.render(<br>&nbsp;&nbsp;<span><span>&lt;<span>ApolloProvider</span>&nbsp;<span>client</span>=<span>{client}</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>&lt;<span>App</span>&nbsp;/&gt;</span><br>&nbsp;&nbsp;<span>&lt;/<span>ApolloProvider</span>&gt;</span></span>,<br>);<br><br></code>

创建 ApolloClient 并设置到 ApolloProvider。

然后在 App.tsx 里用 useQuery 发请求:

<span></span><code><span>import</span>&nbsp;{&nbsp;gql,&nbsp;useQuery&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@apollo/client'</span>;<br><br><span>const</span>&nbsp;getTodoList&nbsp;=&nbsp;gql<span>`<br>&nbsp;&nbsp;query&nbsp;Query&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;todolist&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;content<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>`</span>;<br><br>type&nbsp;TodoItem&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>id</span>:&nbsp;number;<br>&nbsp;&nbsp;content:&nbsp;string;<br>}<br><br>type&nbsp;TodoList&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>todolist</span>:&nbsp;<span>Array</span>&lt;TodoItem&gt;;<br>}<br><br><span>export</span>&nbsp;<span>default</span>&nbsp;<span><span>function</span>&nbsp;<span>App</span>(<span></span>)&nbsp;</span>{<br>&nbsp;&nbsp;<span>const</span>&nbsp;{&nbsp;loading,&nbsp;error,&nbsp;data&nbsp;}&nbsp;=&nbsp;useQuery&lt;TodoList&gt;(getTodoList);<br><br>&nbsp;&nbsp;<span>if</span>&nbsp;(loading)&nbsp;<span>return</span>&nbsp;<span>'Loading...'</span>;<br>&nbsp;&nbsp;<span>if</span>&nbsp;(error)&nbsp;<span>return</span>&nbsp;<span>`Error!&nbsp;<span>${error.message}</span>`</span>;<br><br>&nbsp;&nbsp;<span>return</span>&nbsp;(<br>&nbsp;&nbsp;&nbsp;&nbsp;<span><span>&lt;<span>ul</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data?.todolist?.map(item&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;<span>&lt;<span>li</span>&nbsp;<span>key</span>=<span>{item.id}</span>&gt;</span>{item.content}<span>&lt;/<span>li</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>&lt;/<span>ul</span>&gt;</span></span><br>&nbsp;&nbsp;);<br>}<br></code>

把服务跑起来:

<span></span><code>npm&nbsp;run&nbsp;dev<br></code>

图片

这里涉及到的跨域,现在后端服务里开启下跨域支持:

图片

可以看到,返回了查询结果:

图片

然后加一下新增:

图片

用 useMutation 的 hook,指定 refetchQueries 也就是修改完之后重新获取数据。

调用的时候传入 content 数据。

<span></span><code><span>import</span>&nbsp;{&nbsp;gql,&nbsp;useMutation,&nbsp;useQuery&nbsp;}&nbsp;<span>from</span>&nbsp;<span>'@apollo/client'</span>;<br><br><span>const</span>&nbsp;getTodoList&nbsp;=&nbsp;gql<span>`<br>&nbsp;&nbsp;query&nbsp;Query&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;todolist&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;content<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>`</span>;<br><br><span>const</span>&nbsp;createTodoItem&nbsp;=&nbsp;gql<span>`<br>&nbsp;&nbsp;mutation&nbsp;Mutation($todoItem:&nbsp;CreateTodoItemInput!)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;createTodoItem(todoItem:&nbsp;$todoItem)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;content<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>`</span>;<br><br>type&nbsp;TodoItem&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>id</span>:&nbsp;number;<br>&nbsp;&nbsp;content:&nbsp;string;<br>}<br><br>type&nbsp;TodoList&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>todolist</span>:&nbsp;<span>Array</span>&lt;TodoItem&gt;;<br>}<br><br><span>export</span>&nbsp;<span>default</span>&nbsp;<span><span>function</span>&nbsp;<span>App</span>(<span></span>)&nbsp;</span>{<br>&nbsp;&nbsp;<span>const</span>&nbsp;{&nbsp;loading,&nbsp;error,&nbsp;data&nbsp;}&nbsp;=&nbsp;useQuery&lt;TodoList&gt;(getTodoList);<br><br>&nbsp;&nbsp;<span>const</span>&nbsp;[createTodo]&nbsp;=&nbsp;useMutation(createTodoItem,&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>refetchQueries</span>:&nbsp;[getTodoList]<br>&nbsp;&nbsp;});<br><br>&nbsp;&nbsp;<span>async</span>&nbsp;<span><span>function</span>&nbsp;<span>onClick</span>(<span></span>)&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>await</span>&nbsp;createTodo({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>variables</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>todoItem</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>content</span>:&nbsp;<span>Math</span>.random().toString().slice(<span>2</span>,&nbsp;<span>10</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;<span>if</span>&nbsp;(loading)&nbsp;<span>return</span>&nbsp;<span>'Loading...'</span>;<br>&nbsp;&nbsp;<span>if</span>&nbsp;(error)&nbsp;<span>return</span>&nbsp;<span>`Error!&nbsp;<span>${error.message}</span>`</span>;<br><br>&nbsp;&nbsp;<span>return</span>&nbsp;(<br>&nbsp;&nbsp;&nbsp;&nbsp;<span><span>&lt;<span>div</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>&lt;<span>button</span>&nbsp;<span>onClick</span>=<span>{onClick}</span>&gt;</span>新增<span>&lt;/<span>button</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>&lt;<span>ul</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data?.todolist?.map(item&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;<span>&lt;<span>li</span>&nbsp;<span>key</span>=<span>{item.id}</span>&gt;</span>{item.content}<span>&lt;/<span>li</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>&lt;/<span>ul</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>&lt;/<span>div</span>&gt;</span></span><br>&nbsp;&nbsp;);<br>}<br></code>

测试下:

图片

数据库里也可能看到新增的数据:

图片

这样,我们就能在 react 项目里用 graphql 做 CRUD 了。

案例代码上传了 github。

后端代码: https://github.com/QuarkGluonPlasma/nestjs-course-code/tree/main/graphql-todolist

前端代码:https://github.com/QuarkGluonPlasma/nestjs-course-code/tree/main/graphql-todolist-client

总结

我们实现了Restful 和GraphQL 版的 CRUD。

前端用 React + @apollo/client。

后端用 Nest + GraphQL + Prisma + MySQL。

GraphQL 主要是定义 schema 和 resolver 两部分,schema 是 Query、Mutation 的结构,resolver 是它的实现。

可以在 playground 里调用接口,也可以在 react 里用 @appolo/client 调用。

相比 restful 的版本,graphql 只需要一个接口,然后用查询语言来查,需要什么数据取什么数据,更加灵活。

业务开发中,你会选择用 GraphQL 开发接口么?