前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nest 实现 GraphQL 版 TodoList

Nest 实现 GraphQL 版 TodoList

作者头像
神说要有光zxg
发布2024-04-25 15:05:33
1390
发布2024-04-25 15:05:33
举报
文章被收录于专栏:神光的编程秘籍

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

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

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

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

查询:

新增:

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

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

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

代码语言:javascript
复制
npm install -g @nestjs/cli

nest new graphql-todolist

创建个项目,然后我们首先来实现 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

代码语言:javascript
复制
npm install prisma --save-dev

执行 prisma init 创建 schema 文件:

代码语言:javascript
复制
npx prisma init

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

改下 .env 的配置:

代码语言:javascript
复制
DATABASE_URL="mysql://root:你的密码@localhost:3306/todolist"

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

代码语言:javascript
复制
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

然后创建 model:

代码语言:javascript
复制
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model TodoItem {
  id        Int    @id @default(autoincrement())
  content    String  @db.VarChar(50)
  createTime DateTime @default(now())
  updateTime DateTime @updatedAt
}

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

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

代码语言:javascript
复制
npx prisma migrate dev --name init

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

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

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

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

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

生成一个 service:

代码语言:javascript
复制
nest g service prisma --flat --no-spec

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

代码语言:javascript
复制
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {

    constructor() {
        super({
            log: [
                {
                    emit: 'stdout',
                    level: 'query'
                }
            ]
        })
    }

    async onModuleInit() {
        await this.$connect();
    }
}

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

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

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

代码语言:javascript
复制
import { Inject, Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { CreateTodoList } from './todolist-create.dto';
import { UpdateTodoList } from './todolist-update.dto';

@Injectable()
export class AppService {

  getHello(): string {
    return 'Hello World!';
  }

  @Inject(PrismaService)
  private prismaService: PrismaService;

  async query() {
    return this.prismaService.todoItem.findMany({
      select: {
        id: true,
        content: true,
        createTime: true
      }
    });
  }

  async create(todoItem: CreateTodoList) {
    return this.prismaService.todoItem.create({
      data: todoItem,
      select: {
        id: true,
        content: true,
        createTime: true
      }
    });
  }

  async update(todoItem: UpdateTodoList) {
    return this.prismaService.todoItem.update({
      where: {
        id: todoItem.id
      },
      data: todoItem,
      select: {
        id: true,
        content: true,
        createTime: true
      }
    });
  }

  async remove(id: number) {
    return this.prismaService.todoItem.delete({
      where: {
        id
      }
    })
  }
}

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

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

todolist-create.dto.ts

代码语言:javascript
复制
export class CreateTodoList {
    content: string;
}

todolist-update.dto.ts

代码语言:javascript
复制
export class UpdateTodoList {
    id: number;
    content: string;
}

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

代码语言:javascript
复制
import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common';
import { AppService } from './app.service';
import { CreateTodoList } from './todolist-create.dto';
import { UpdateTodoList } from './todolist-update.dto';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Post('create')
  async create(@Body() todoItem: CreateTodoList) {
    return this.appService.create(todoItem);
  }

  @Post('update')
  async update(@Body() todoItem: UpdateTodoList) {
    return this.appService.update(todoItem);
  }

  @Get('delete')
  async delete(@Query('id') id: number) {
    return this.appService.remove(+id);
  }

  @Get('list')
  async list() {
    return this.appService.query();
  }

}

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

把服务跑起来试一下:

代码语言:javascript
复制
npm run start:dev

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

然后添加一个:

服务端打印了 insert into 的 sql:

数据库也有了这条记录:

再加一个:

然后查一下:

接下来试下修改、删除:

再查一下:

没啥问题。

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

接下来实现 graphql 版本:

安装用到的包:

代码语言:javascript
复制
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql

然后在 AppModule 里引入下:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaService } from './prisma.service';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver } from '@nestjs/apollo';

@Module({
  imports: [
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      typePaths: ['./**/*.graphql'],
    })
  ],
  controllers: [AppController],
  providers: [AppService, PrismaService],
})
export class AppModule {}

typePaths 就是 schema 文件的路径:

添加一个 todolist.graphql

代码语言:javascript
复制
type TodoItem {
    id: Int
    content: String
}

input CreateTodoItemInput {
  content: String
}

input UpdateTodoItemInput {
  id: Int!
  content: String
}

type Query {
  todolist: [TodoItem]!
  queryById(id: Int!): TodoItem
}


type Mutation {
  createTodoItem(todoItem: CreateTodoItemInput!): TodoItem!
  updateTodoItem(todoItem: UpdateTodoItemInput!): TodoItem!
  removeTodoItem(id: Int!): Int
}

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

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

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

代码语言:javascript
复制
nest g resolver todolist --no-spec --flat
代码语言:javascript
复制
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { PrismaService } from './prisma.service';
import { Inject } from '@nestjs/common';
import { CreateTodoList } from './todolist-create.dto';
import { UpdateTodoList } from './todolist-update.dto';

@Resolver()
export class TodolistResolver {

    @Inject(PrismaService)
    private prismaService: PrismaService;

    @Query("todolist")
    async todolist() {
        return this.prismaService.todoItem.findMany();
    }

    @Query("queryById")
    async queryById(@Args('id') id) {
        return this.prismaService.todoItem.findUnique({
            where: {
                id
            }
        })
    }

    @Mutation("createTodoItem")
    async createTodoItem(@Args("todoItem") todoItem: CreateTodoList) {
        return this.prismaService.todoItem.create({
            data: todoItem,
            select: {
              id: true,
              content: true,
              createTime: true
            }
          });
    }


    @Mutation("updateTodoItem")
    async updateTodoItem(@Args('todoItem') todoItem: UpdateTodoList) {
        return this.prismaService.todoItem.update({
            where: {
              id: todoItem.id
            },
            data: todoItem,
            select: {
              id: true,
              content: true,
              createTime: true
            }
          });
    }

    @Mutation("removeTodoItem")
    async removeTodoItem(@Args('id') id: number) {
        await this.prismaService.todoItem.delete({
            where: {
              id
            }
        })
        return id;
    }
}

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

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

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

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

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

我们换一个:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaService } from './prisma.service';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver } from '@nestjs/apollo';
import { TodolistResolver } from './todolist.resolver';
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';

@Module({
  imports: [
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      typePaths: ['./**/*.graphql'],
      playground: false,
      plugins: [ApolloServerPluginLandingPageLocalDefault()],
    })
  ],
  controllers: [AppController],
  providers: [AppService, PrismaService, TodolistResolver],
})
export class AppModule {}

试一下新增:

查询:

修改:

单个查询:

删除:

查询:

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

然后在 react 项目里调用下。

代码语言:javascript
复制
npx create-vite

进入项目,安装 @apollo/client

代码语言:javascript
复制
npm install

npm install @apollo/client

改下 main.tsx

代码语言:javascript
复制
import * as ReactDOM from 'react-dom/client';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import App from './App';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql',
  cache: new InMemoryCache(),
});

const root = ReactDOM.createRoot(document.getElementById('root')!);

root.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
);

创建 ApolloClient 并设置到 ApolloProvider。

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

代码语言:javascript
复制
import { gql, useQuery } from '@apollo/client';

const getTodoList = gql`
  query Query {
    todolist {
      content
      id
    }
  }
`;

type TodoItem = {
  id: number;
  content: string;
}

type TodoList = {
  todolist: Array<TodoItem>;
}

export default function App() {
  const { loading, error, data } = useQuery<TodoList>(getTodoList);

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <ul>
      {
        data?.todolist?.map(item => {
          return <li key={item.id}>{item.content}</li>
        })
      }
    </ul>
  );
}

把服务跑起来:

代码语言:javascript
复制
npm run dev

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

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

然后加一下新增:

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

调用的时候传入 content 数据。

代码语言:javascript
复制
import { gql, useMutation, useQuery } from '@apollo/client';

const getTodoList = gql`
  query Query {
    todolist {
      content
      id
    }
  }
`;

const createTodoItem = gql`
  mutation Mutation($todoItem: CreateTodoItemInput!) {
    createTodoItem(todoItem: $todoItem) {
      id
      content
    }
  }
`;

type TodoItem = {
  id: number;
  content: string;
}

type TodoList = {
  todolist: Array<TodoItem>;
}

export default function App() {
  const { loading, error, data } = useQuery<TodoList>(getTodoList);

  const [createTodo] = useMutation(createTodoItem, {
    refetchQueries: [getTodoList]
  });

  async function onClick() {
    await createTodo({
      variables: {
        todoItem: {
          content: Math.random().toString().slice(2, 10)
        }
      }
    })
  }

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <div>
      <button onClick={onClick}>新增</button>
      <ul>
        {
          data?.todolist?.map(item => {
            return <li key={item.id}>{item.content}</li>
          })
        }
      </ul>
    </div>
  );
}

测试下:

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

这样,我们就能在 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 开发接口么?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神光的编程秘籍 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档