graphQL 学习记录

范金鑫
2023-12-01

graphQL 学习记录

服务端采用 apollo-server 实现

客户端采用 apollo-clint + vue3.0 实现

数据库采用 notarealdb 模拟

服务端创建

初始化

  • 初始化项目
mkdir session2
cd session2
yarn init 
# 根据提示补充完整信息
  • 安装必备模块
yarn add apollo-server graphql notarealdb
# apollo-server 可以直接启动服务端,不需要使用服务端框架
# graphql 必备的
# notarealdb 通过JSON文件 模拟数据库 也可以使用真实的数据库
  • 添加启动代码
  "scripts": {
    "dev": "nodemon ."
  },
  • 最终的package.json文件
{
  "name": "session2",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "node ."// "nodemon ."  // 需要安装yarn add nodemon --save 或者全局安装 作用是修改了代码自动重启服务 
  },
  "dependencies": {
    "apollo-server": "^3.5.0",
    "graphql": "^16.0.1",
    "notarealdb": "^0.2.2"
  }
}
  • 准备数据文件
// students.json
[
  {
    "id": "Sy0bHHgqY",
    "name": "张三",
    "age": 18,
    "sex": "男",
    "classID": 1
  },
  {
    "id": "SJGB8re5Y",
    "name": "李四",
    "age": 20,
    "sex": "男",
    "classID": 2
  },
  {
    "id": "B1MnIrecF",
    "name": "赵六",
    "age": 22,
    "sex": "男",
    "classID": 3
  },
  {
    "id": "BkXePHx9K",
    "name": "王五",
    "age": 22,
    "sex": "男",
    "classID": 3
  }
]

// class.json
[{
    "id":1,
    "name":"col_01",
    "description":"三年一班"
},{
    "id":2,
    "name":"col_02",
    "description":"三年二班"
},{
    "id":3,
    "name":"col_03",
    "description":"三年三班"
}]

  • 模拟数据库
// db.js
const { DataStore } = require('notarealdb');

const store = new DataStore('./data'); // 这里是因为将之前的两个json文件放到了data文件夹中

module.exports = {
   students:store.collection('students'),
   class:store.collection('class')
};

  • 准备graphql的schema
# session.gql    
# # 是注释

# 自定义类型
type Student{
    id:ID!    # !表示必须有的字段
    name:String
    age: Int
    sex: String
    class:Class  # 自定义类型中的查询方法
}

# 自定义类型
type Class{
    id:Int!
    name:String
    description:String
}

# 通用的查询方法定义
type Query{
    students:[Student]
    studentsByName(id:String):Student
    classes:[Class]
    classById(id:ID):Class
}
# 自定义的参数类型 可以做参数校验
input InputStudent{
    name:String
    age:Int
    sex:String
    classID:Int
}
# 增 删 改 都在这里定义
type Mutation{
    createStudent(s:InputStudent):String
    updateStudent(s:InputStudent):[Student]
}
  • 准备
// session.js
const db = require('../db')
// 定义查询函数  与schema 中的Query对应   schema中有的可以不在此处实现 但是这里出现的必须是在schema中定义了的
const Query = { 
    students:()=> { 
        return db.students.list();
    },
    studentsByName:(root,args,context,info)=>{
        return db.students.get(args.id)
    },
    classes:(root,args,context,info)=>{
        return db.class.list();
    }
}

// 当自定义的类型中有查询 需要定义该类型的查询  这里的Student 与 schema 中的 Student类型对应
const Student = {
    class:(root,args,context,info)=>{
        return db.class.get(root.classID)
    }
}

// 修改 与schema中的Mutation对应
const Mutation = {
    createStudent:(root,{s},context,info)=>{
        return db.students.create(s)
    },
    updateStudent:(root,{s},context,info)=>{
        db.students.update(s)
        return db.students.list();
    }
}

module.exports = {
    Query,
    Student,
    Mutation
}
  • 主文件编写
const fs = require('fs')
// 读取schema
const typeDefs = fs.readFileSync("./gql/session.gql", { encoding: 'utf-8' })
// 导入resolver
const resolvers = require('./resolvers/session1')

const { ApolloServer } = require('apollo-server');
const { ApolloServerPluginLandingPageGraphQLPlayground } = require("apollo-server-core");

// 生成服务端应用
const app = new ApolloServer({ 
    typeDefs, 
    resolvers, 
    plugins: [ 
        ApolloServerPluginLandingPageGraphQLPlayground   // 开启测试页面
    ],
})

// 启动服务
app.listen(8000).then(({url}) => {
    console.log(url);
})

  • 项目布局
    – session
    – data
    – class.json
    – students.json
    – gql
    – session.gql
    – resolvers
    – session.js
    – db.js
    – index.js
    – package.json
  • 启动服务
# 在index.js 所在的文件夹中执行
yarn dev
# 查询
query{
    students{
        id           # 这里根据需要的字段查询
        name
        age
        sex
        class{    # 因为class字段返回的是Class类 所以也需要指定字段
            id
            description
        }
    }
}
# 也可以多个查询一起
query{
    students{         # 
        name
        class{
            description
        }
    },
    classes{          # 
        id
        description
    }
}
# mutation
mutation{
    createStudent(s:{name:"张三",age:10,sex:"男",classID:1})  
     # 传参必须是k:v方式  参数类型是对象 不是所有的参数字段都是必须的
     # 返回值是字符串,所以不需要选字段
}

客户端创建

客户端使用vue3.x创建

  • 新建vue项目
vue create graphql
# 根据提示选择   我这里选择了vue3 default
  • 安装必备的包
yarn add graphql graphql-tag apollo-boost 
  • vue设置代理
    cors跨域请求 前端设置代理
// vue.config.js
module.exports ={
    devServer: {
        proxy:{
            '/api':{
                target:"http://127.0.0.1:8000",  // 这里是服务端的地址
                changeOrigin:true,
                pathRewrite:{
                    '^/graphql':'/graphql'  // 这里是代理uri:正式的uri 真实的就是在这个路由,不是调试页面的路由
                }
            }
        }
    }
}
  • 封装graphql客户端
// script/graphql.js
import { HttpLink } from "apollo-link-http"
import ApolloClient from "apollo-client"
import { InMemoryCache } from 'apollo-cache-inmemory'
import gql from "graphql-tag";

const httpLink = new HttpLink({
    uri: "/api/graphql"
})
/*    这样创建的客户端 同样的请求会使用缓存,得不到最新的数据  如果是只需要初始化那就可以使用这个
const apolloClient = new ApolloClient({
    link: httpLink,
    cache: new InMemoryCache(),
    connectToDevTools: true,
})
*/

/* 构造新的客户端 */
function getClint(){
    return new ApolloClient({
        link: httpLink,
        cache: new InMemoryCache(),
        connectToDevTools: true,
    })
}

/**用于查询的 */
const clintQuery = (queryStr) => {
    /**每次都是用新的客户端 */
    return getClint().query({ query: gql(queryStr) })
}

/**用于修改的  用于修改的其实可以使用单例客户端 */
const clintMutation = (mutation, variables) => {
    return getClint().mutate({ mutation: gql(mutation), variables: variables })
}

export { clintQuery, clintMutation } 
  • graphql客户端组件
    这里直接修改了HelloWord组件 删除了不必要的元素
<template>
  <div>
        <div>
      <div class="input">
        Mutation
        <div>
          <label for="name">姓名:</label>
          <input type="text" id="name" v-model="stuName" />
        </div>
        <div>
          <label for="age">年龄:</label>
          <input type="number" id="age" v-model="age" />
        </div>
        <div class="slt">
          <span>
            <label for="sex">性别:</label>
            <select id="sex" name="sex" v-model="sex">
              <option value="男">男</option>
              <option value="女">女</option>
            </select>
          </span>
          <span>
            <label for="cls">班级:</label>
            <select id="cls" v-model="cls">
              <option :value="item.id" v-for="item in classes" :key="item.id">
                {{ item.description }}
              </option>
            </select>
          </span>
        </div>

        <input
          type="button"
          value="创建"
          @click="
            createStudent();
            reset();
          "
        />
      </div>
    </div>
    <hr>
    Query
    <table class="stu">
      <thead>
        <tr>
          <th>ID</th>
          <th>姓名</th>
          <th>班级</th>
        </tr>
      </thead>
      <tbody>
        <transition-group name="stuList">
          <tr v-for="item in students" :key="item.id">
            <td>{{ item.id }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.class }}</td>
          </tr>
        </transition-group>
      </tbody>
    </table>
    <div></div>
  </div>
</template>

<script>
import { clintQuery, clintMutation } from "../script/graphql";
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      students: [],
      stuName: "",
      age: 0,
      sex: "男",
      cls: "",
      classes: [],
    };
  },
  components: [],
  methods: {
    getStudents: function () {
      // 不要使用箭头函数 否则无法获取this
      let that = this; // 由于在异步中 this 重置,所以提前将this赋值给that 以便后面获取到数据
      // 调用封装好的graphql 客户端发送请求
      clintQuery(`
        query {
          students {
            id
            name
            sex
            class {
              description
            }
          }
        }
      `).then(function (rst) {
        let students = [];
        for (let i = 0; i < rst.data.students.length; i++) {
          students.push({
            id: rst.data.students[i].id,
            name: rst.data.students[i].name,
            sex: rst.data.students[i].sex,
            class: rst.data.students[i].class.description,
          });
        }
        that.students = students;
      });
    },
    getClasses: function () {
      let that = this;
      clintQuery(`
        query {
          classes {
            id
            description
          }
        }
      `).then(function (rst) {
        that.classes = rst.data.classes;
      });
    },
    createStudent: function () {
      let that = this;
      // 第一个参数是执行的graphql查询字符串,第二个参数是传参
      clintMutation(
        `
        mutation createStudent($name:String,$age:Int,$sex:String,$classID:Int) {
          createStudent(s:{name:$name,age:$age,classID:$classID,sex:$sex})
        }
      `,
        {
          name: that.stuName,
          age: that.age,
          sex: that.sex,
          classID: that.cls,
        }
      ).then(() => that.getStudents());
    },
    reset: function () {
      this.stuName = "";
      this.age = 0;
      this.cls = "";
      this.sex = "";
    },
  },
  mounted() {
    this.getStudents();
    this.getClasses();
  },
  watch: {},
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.stu {
  width: 400px;
  margin: 0 auto;
}
.stu tbody{
  width:100%;
  height: 300px;
  overflow: auto;
}
.stu tbody tr {
  height: 20px;
}
.stu thead {
  background-color: #b6b6b6;
}
.stu th {
  width: 100px;
}

tbody tr:nth-child(2n-1) {
  background-color: rgba(133, 158, 133,.6);
}

.input {
  width: 400px;
  margin: 0 auto;
}

.input .slt {
  margin: 0 auto;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  width: 56%;
}

.stuList-item{
  /* opacity: 1; */
  transition: all 0.8s ease;
}

.stuList-enter-active,
.stuList-leave-active {
  transition: all 0.5s ease;
}

.stuList-enter_from,
.stuList-leave-to{
  opacity: 0;
  transform: translateY(10px);
}

</style>

  • 启动客户端
yarn serve
  • 打开页面调试

http://localhost:8080/

最后的最后 所有的代码都放在了 https://gitee.com/mslsy/graphql

 类似资料: