- 相较于传统的restful风格来开发crud。
- 现在尝试下graphql风格来试试。
Nestjs+Graphql来实现一个todolist
npm i -g @nestjs/cli
nest new graphql-todolist
然后我们使用docker 跑一个mysql
docker pull mysql
docker run -d -name mysql-c -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
然后可以使用vscode插件Database Client JDBC查看连接查看自己数据
CREATE DATABASE todolist
DEFAULT CHARACTER SET = 'utf8mb4';
在nest里面使用prisma连接mysql
npm i prisma --save-dev
初始化prisma创建schema文件
npx prisma init
然后再.env文件修改配置
DATABASE_URL="mysql://root:123456@localhost:3306/todolist"
在prisma/schema.prisma 添加model
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
}
执行 prisma migrate dev,它会根据定义的 model 去创建表
npx prisma migrate dev --name init
接下来可以在nest里面写crud了 使用nest 生成一个service
nest g service prisma --flat --no-spec
修改PrismaService配置,继承PrismaClient, 得到父类的crud api
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()
}
}
接下来安装graphql要使用的包
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql
在appModule引入
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';
@Module({
imports: [
GraphQLModule.forRoot({
driver: ApolloDriver,
typePaths: ['./**/*.graphql']
})
],
controllers: [AppController],
providers: [AppService, PrismaService, TodolistResolver],
})
export class AppModule { }
添加一个 todolist.graphql
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
}
实现 resolver,也就是这些接口的实现
nest g resolver todolist --no-spec --flat
import { Inject, } from '@nestjs/common';
import { Args, Mutation, Query } from "@nestjs/graphql";
import { Resolver } from '@nestjs/graphql';
import { PrismaService } from './prisma.service';
import { UpdateTodoList } from './dto/todolist-update.dto';
import { CreateTodoList } from './dto/todolist-create.dto';
@Resolver()
export class TodolistResolver {
@Inject(PrismaService)
private prismaService: PrismaService
@Query('todolist')
todolist() {
return this.prismaService.todoItem.findMany()
}
@Query('queryById')
async queryById(@Args('id') id) {
return await this.prismaService.todoItem.findUnique({
where: {
id
}
})
}
@Mutation('updateTodoItem')
updateTodoItem(@Args('todoItem') todoItem: UpdateTodoList) {
return this.prismaService.todoItem.update({
where: {
id: todoItem.id
},
data: todoItem,
})
}
@Mutation('createTodoItem')
createTodoItem(@Args('todoItem') todoItem: CreateTodoList) {
return this.prismaService.todoItem.create({
data: todoItem,
})
}
@Mutation('removeTodoItem')
async removeTodoItem(@Args('id') id: number) {
const res = await this.prismaService.todoItem.findFirst({
where: {
id
}
})
if (res) {
await this.prismaService.todoItem.delete({
where: {
id
}
})
return id
} else {
return 'id is not exist'
}
}
}
用 @Resolver 声明 resolver,用 @Query 声明查询接口,@Mutation 声明增删改接口,@Args 取传入的参数。 具体增删改查的实现和之前一样。 浏览器访问 http://localhost:3000/graphql 就是 playground,可以在这里查询
基于Graphql的crud完成了
vue实现
npm create vite@latest
初始化之后再继续安装
npm install --save graphql graphql-tag @apollo/client @vue/apollo-composable
创建apolloClient.ts文件
touch apploClient.ts
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core'
const httpLink = new HttpLink({
// You should use an absolute URL here
uri: 'http://localhost:3000/graphql',
})
// Create the apollo client
export const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
})
引入main.ts
import { createApp, h, provide } from 'vue'
import './style.css'
import App from './App.vue'
import { apolloClient } from "./apolloClient";
import { DefaultApolloClient } from '@vue/apollo-composable'
const app = createApp({
setup() {
provide(DefaultApolloClient, apolloClient)
},
render: () => h(App)
})
app.mount('#app')
实现crud
<script setup lang="ts">
import { ref, onMounted, watchEffect, unref, reactive, nextTick, computed, watch } from "vue";
import { useQuery, provideApolloClient, useMutation } from "@vue/apollo-composable";
import { apolloClient } from "./apolloClient";
import gql from "graphql-tag";
const todoListRef = ref()
const loadingRef = ref<boolean>(false)
const tableRefetch = ref()
const getTodoList = () => {
const GET_TODOITEM_LIST = gql`
query queryTodolist {
todolist {
content
id
}
}
`
const { loading, onResult, refetch } = useQuery(GET_TODOITEM_LIST)
loadingRef.value = loading.value
tableRefetch.value = refetch
onResult((res) => {
if (!res.loading) {
const { data: result } = res
todoListRef.value = result?.todolist
}
})
}
const inputModel = ref()
const getTodoItemById = async (id: number) => {
const GET_TODOITEM = gql`
query queryTodoItem($queryById: Int!) {
queryById(id: $queryById){
content
}
}
`
const variables = {
"queryById": id
}
const { onResult } = provideApolloClient(apolloClient)(() => useQuery(GET_TODOITEM, variables))
onResult((res) => {
if (!res.loading) {
const { data: result } = res
inputModel.value = unref(result).queryById.content
}
})
}
// addFunc
const addFunc = async () => {
const ADD_TODOITEM = gql`
mutation Mutation($todoItem: CreateTodoItemInput!) {
createTodoItem(todoItem: $todoItem){
content,
id
}
}
`
const variables = {
"todoItem": {
content: inputModel.value
}
}
const { mutate, onDone, loading } = provideApolloClient(apolloClient)(() => useMutation(ADD_TODOITEM, {
variables,
}))
await mutate()
tableRefetch.value()
}
// delFunc
const delFunc = async (id: number) => {
const DEL_TODOITEM = gql`
mutation Mutation($removeTodoItemId: Int!) {
removeTodoItem(id: $removeTodoItemId)
}
`
const variables = {
"removeTodoItemId": id
}
const { mutate, onDone, loading } = provideApolloClient(apolloClient)(() => useMutation(DEL_TODOITEM, {
variables,
}))
await mutate()
tableRefetch.value()
}
// updateFunc
const updateFunc = async (id: number) => {
const UPDATE_TODOITEM = gql`
mutation Mutation($todoItem: UpdateTodoItemInput!) {
updateTodoItem(todoItem: $todoItem){
content,
id
}
}
`
const variables = {
"todoItem": {
content: inputModel.value,
id
}
}
const { mutate, onDone, loading } = provideApolloClient(apolloClient)(() => useMutation(UPDATE_TODOITEM, {
variables,
}))
await mutate()
}
onMounted(() => {
getTodoList()
})
</script>
<template>
<button @click="addFunc">add</button>
<div v-for="item in todoListRef">
<span @click="getTodoItemById(item!.id)">{{ item?.content }}-{{ item?.id }}</span>
<button @click="delFunc(item!.id)">del</button>
<button @click="updateFunc(item!.id)">edit</button>
</div>
<input v-model="inputModel" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
配置代码生成
npm add -D typescript @graphql-codegen/cli @graphql-codegen/client-preset
# 初始化配置文件
npx graphql-code-generator init
配置生成参考codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:3000",
documents: "src/**/*.vue",
generates: {
"src/gql/": {
preset: "client",
plugins: []
},
"./graphql.schema.json": {
plugins: ["introspection"]
}
}
};
export default config;
修改package.json,在scripts中增加
"generate": "graphql-codegen"