GraphQL 的 正确打开方式 (apollo-client前戏)

192 阅读5分钟

如果你要学习 Apollo Client 和 Apollo Server就请先学会GraphQL,因为 Apollo Client 和 Apollo Server是GraphQL不同场景下的封装,目前关于 Apollo Client 和 Apollo Server的文章特别少,希望本文能对你有一点帮助。

一.介绍

GraphQLFacebook开发的一种数据查询语言,它于2015年公开发布,是REST API的替代品。

GraphQL既是一种用于API的查询语言,也是一个能满足你查询数据时候,运行数据的语言。GraphQL 对你的API中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让API更容易地随着时间推移而演进。

● 官网: graphql.org/

● 中文网: graphql.cn/

● 特点:

1.请求需要的数据,不多不少。 例如: account中有name, lage, sex, department等。 可以只取得需要的字段。

2.获取多个资源,只用-个请求。

3.描述所有可能类型的系统。便于维护,根据需求平滑演进,添加或者隐藏字段。

二.使用

为了方便学习,我们先启动一个简单的node服务,然后写一个简单的html页面,体验一把graphql,看看他有哪些需要我们注意的东西。

1.创建一个文件夹,初始化项目

npm init -y

2.安装资源

npm i express graphql mysql express-graphql

3.解决跨域问题

npm i cors

项目目录如下:

image.png

4.服务端代码

说明:graphql官网里面也有一个express的例子,我没有跑起来,你们可以跑起来,因为时间原因,我选择了放弃,使用自己熟悉的包体验了一把graphql,你们可以自己试试官网的案例,原理雷同。

image.png

在新项目里面创建serve.js文件,添加如下代码:

var express = require('express');
var { buildSchema } = require('graphql');
const { graphqlHTTP } = require('express-graphql');
const cors = require('cors');

var app = express();
app.use(cors());
// 定义模型 查询 type不能丢
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);
// 定义查询对应的处理器
const root = {
  hello: () => {
    return "hello world";
  },
};

/* GET users listing. */
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(3009, () => {
  console.log("开启3009成功");
});

// 向外公开文件夹供用户访问静态资源index
app.use(express.static('public'));


执行命令 node serve.js 启动服务 3009

代码解释:

graphqlHTTP服务器有2部分组成,一个是schema,一个是root

schema里面放的是定义数据的类型。

root里面是数据,一般这个数据可能来自于后端java或者是mysql数据库等,为了方便学习,我直接将数据类型都定死了。

image.png

5.客户端代码

创建index.js文件,添加如下代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button onclick="getdata()">获取数据</button>
    <script>
        function getdata() {
            const query = `query{ hello }` //字符串
            fetch('http://127.0.0.1:3009/graphql', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                //   转换为字符串
                body: JSON.stringify({
                    query:query,
                   // variables: {id}
                })
            }).then(res => res.json).then(data => {
                console.log(data)
            })
        }
    </script>
</body>

</html>

启动前端服务,如下:

右键 open with liveserver 启动前端服务。如果没有,就用http-server也行。

image.png

测试

image.png

代码解释:

前端就是要写个gql,然后把gql放到http请求里面去获取数据。

image.png

三.上述总结

graphql的使用有三部分组成:

node 那边 graphqlHTTP利用schema和root创建一个graphql服务出来。

前端向graphql服务发起 post 请求,然后将gql语句以参数的形式传给graphql服务即可。

四.类型

4.1基础类型和对象类型

对graphql而言,他的类型很简单,基础类型就五种:GraphQL 结构语言支持 StringIntFloatBoolean 和 ID ,还有一些高级类型,比如数组,对象,集合,当然如果你要传参数,写法也要注意。

如果你学过TS那么你学这个将会很快。其实最难得应该是 gql 的写法。

测试gql看看,服务端:server.js

var express = require('express');
var { buildSchema } = require('graphql');
const { graphqlHTTP } = require('express-graphql');
const cors = require('cors');

var app = express();
app.use(cors());
// 定义模型 查询 type不能丢
const schema = buildSchema(`
  type Query {
    hello: String
    num: Int
    count: Float
    isTrue: Boolean
    getId: ID
    getList: List
    getArr: [Int]
    getObjList: [List]
    getName(id: Int): String
  }

  type List {
    id: Int
    name: String
  }
`);
// 定义查询对应的处理器
const root = {
  //String
  hello: () => {
    return "hello world";
  },
  //Int
  num: () => {
    return 123;
  },
  //Float
  count: () => {
    return 12.33;
  },
  //Boolean
  isTrue: () => {
    return false;
  },
  //ID
  getId: () => {
    return '78776hhhgg6yggj2';
  },
  //对象
  getList: () => {
    return {
      id: 12,
      name: "章三"
    };
  },
  //数组
  getArr: () => {
    return [1, 2, 3, 4, 5, 6, 7, 8, 9];
  },
  //集合
  getObjList: () => {
    return [{
      id: 12,
      name: "张三"
    }, {
      id: 13,
      name: "李四"
    }];
  },
  //传参
  getName: ({ id }) => {
    return id === 1 ? '李四' : '王五';
  }
};

/* GET users listing. */
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(3009, () => {
  console.log("开启3009成功");
});

// 向外公开文件夹供用户访问静态资源index
app.use(express.static('public'));


index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button onclick="getdata()">获取数据</button>
    <script>
        function getdata() {
            //const query = `query{ hello }` //String
            //const query = `query{ num }` //Int
            // const query = `query{ count }` //Float
            //const query=`query{ isTrue }` //Boolean
            //const query=`query{ getId }` //ID

            //const query = `query{ getList {id,name} }` //对象
            //const query = `query{ getArr }` //数组
            //集合和获取对象的写法一样
            //const query = `query{ getObjList{id,name}}` 

            //不要往query里面传递变量,最好利用$ 和 variables相互结合
            //const query = `query {getName(id: 5)}`//传参数,官网不建议
            //正确传惨
            const id = 5;
            const query = `query GetName($id: Int){
               getName(id: $id)
            }`

            fetch('http://127.0.0.1:3009/graphql', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                body: JSON.stringify({
                    query:query,
                    variables: {id}
                })
            }).then(res => res.json).then(data => {
                console.log(data)
            })
        }
    </script>
</body>

</html>


4.2 基础类型注意事项

上面的案例囊括了所有的数据类型,及其他们对应的基础gql写法,你需要注意的有以下几个:

4.1.1.注意数组,对象,集合的类型定义

数组直接用中括号即可,[Int],[String]

对象你单独定义一个type,思想类似typescript

image.png

4.1.2.如果你的数据返回值是对象或者集合,他们的gql写法是一样的。

image.png

4.1.3.如果你要给query传递参数的话,请用$variables,而不是将值拼接到字符串模版里面去。

image.png

4.1.4.传参时候的gql的写法和其他不一样你要注意下:

没有参数的时候,query后面是一个对象,传参数的时候query后面是一个函数,在这个函数里面就要把带$的参数定义好。

image.png

4.3 变更输入类型

graphql服务schema里面定义了很多类型,这些类型都是以type Query开头的,当我们定义它是一个对象的时候,以type List开头。一般我们会将 root 里面定义的数据的类型都放到Query里面去。如图:

image.png

但是从业务来讲,我们系统的每个模块都是由基础的增,删,改,查组成,为了区分增删改查的功能,graphqlQuery做了划分,有:Mutation,Query 两类,你可以把删除,修改,新增放到Mutation里面,你可以把查找放到Query里面。

关键字inputtypeinput是用来定义输入类型的,新增和修改的输入类型一样,为了和其他参数类型做分区,你就可以使用input定义。不做强制,input定义的类型可以用type替换。

为了简单,你可以全部使用type

image.png

gql使用的时候就将原来的query换成mutation就好了

image.png

五. graphql和mysql相互连接

mysql和node相互连接的前提条件是你的电脑上要有mysql数据库才行,不然就是白扯。

代码如下:

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const mysql = require('mysql2');

const app = express();

// mysql的实际信息,你要添加上,你要确保你的电脑上有mysql数据库和对应的表,不然你就搞个寂寞!
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'mydatabase'
});

connection.connect((err) => {
  if (err) {
    console.error('Error connecting to MySQL: ', err);
    return;
  }
  console.log('Connected to MySQL database');
});

const schema = buildSchema(`
  type User {
    id: Int
    name: String
    age: Int
  }

  type Query {
    getUser(id: Int): User
  }
`);

const root = (args) => {
  return new Promise((resolve, reject) => {
    connection.query('SELECT * FROM users WHERE id = ?', [args.id], (err, results) => {
      if (err) {
        reject(err);
      } else {
        resolve(results[0]);
      }
    });
  });
};

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(3009, () => {
  console.log('GraphQL server running on port 3009');
});

你要安装上mysql数据库就能使用了。

六.一个简单的增删改查案例

node端

在上面项目里面建立 user-server.js文件

var express = require('express');
var { buildSchema } = require('graphql');
const { graphqlHTTP } = require('express-graphql');
const cors = require('cors');
const crypto = require("crypto"); //node的加密模块

var app = express();
app.use(cors());
// 定义模型 查询 type不能丢
const schema = buildSchema(` 
  type User {
    name: String
    sex: String
    age: Int
    id: ID
  }
 
  type Query {
    getList: [User]
    getUser(id: ID): User
  }
 
  type Mutation {
    createUser(name: String, sex: String, age: Int): User
    updateUser(id: ID, name: String, sex: String, age: Int): User
    deleteUser(id: ID): User
  }
`);

class User {
  constructor({ id, name, sex, age }) {
    this.id = id;
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
}

var fakeDatabase = [];

var root = {
  createUser({ name, sex, age }) {
    var id = crypto.randomBytes(10).toString("hex");

    const user = new User({ id, name, sex, age });
    fakeDatabase.push(user);

    return user;
  },
  updateUser({ id, name, sex, age }) {
    console.log(id, name, sex, age, 7878);

    const user = new User({ id, name, sex, age });
    const userList = fakeDatabase.map((item) => {
      if (item.id === id) {
        return user;
      }
      return item;
    });

    fakeDatabase = [...userList];

    return user;
  },
  deleteUser({ id }) {
    const userList = fakeDatabase.filter((item) => item.id !== id);
    fakeDatabase = [...userList];

    return userList;
  },
  getList() {
    return fakeDatabase;
  },
  getUser({ id }) {
    const user = fakeDatabase.find((item) => item.id === id);

    return user;
  }
};

/* GET users listing. */
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(3009, () => {
  console.log("开启3009成功");
  console.log(fakeDatabase);
});

// 向外公开文件夹供用户访问静态资源index
app.use(express.static('public'));

写好以后记得用node启动

客户端

建立:user-client.js

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
      <h1>创建人员</h1>
      <form id="userForm">
        姓名(string)<input type="text" name="name" /><br/>
        性别(string)<input type="text" name="sex" /><br/>
        年龄(int) <input type="text" name="age" /><br/>
      </form>
      <button type="submit" onclick="save()">保存</button>
    </div>

    <div>
      <h1>查询数据</h1>
      <button onclick="getData()">查询List</button>
      <div id="userList"></div>
    </div>

    <div>
      <h1>修改表单</h1>
      <form id="updateForm">
        id(ID) <input type="text" name="id"  id="updateId"/><br/>
        姓名(string) <input type="text" name="name"  id="updateName"/><br/>
        性别(string)<input type="text" name="sex" id="updateSex"/><br/>
        年龄(int)<input type="text" name="age" id="updateAge"/><br/>
      </form>

      <button type="submit" onclick="updateUser()">修改用户</button>
    </div>

    <script>
        const request = ({
          query, 
          variables
        })=>{
          return fetch("http://127.0.0.1:3009/graphql", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Accept: "application/json",
            },
            body: JSON.stringify({
              query,
              variables
            }),
          })
        }

        function save() {
          const  forms = document.getElementById("userForm");
          const user = {};
          for (var i = 0; i < forms.elements.length; i++) {
          	var input = forms.elements[i];
            if(input.name === 'age'){
              user[input.name] = Number(input.value)
            }else{
              user[input.name] = String(input.value)
            } 
          }
        
          var query = /* GraphQL */`mutation CreateUser($name: String, $sex: String, $age: Int) {
            createUser(name: $name, sex: $sex, age: $age) {
              name
              sex
              age
            }
          }`

          request({
            query,
            variables: user,
          })
          .then(r => r.json())
          .then(data => {
            getData()
            alert('添加成功!')
          })
        }

        function getData() {
          var query = `query {
            getList {
              id
              name
              sex
              age
            }
          }`

          request({
            query
          })
          .then(r => r.json())
          .then(({data}) => {
            const list = data.getList;
            const userList = document.getElementById("userList");
            userList.innerHTML=''
          
            list.forEach((item)=>{
              const newDiv = document.createElement("div");
              newDiv.innerHTML = `<div>
                <span >${item.id}</span> 
                <span>${item.name}</span>
                <span>${item.sex}</span>
                <span>${item.age}</span>
                <button onclick="deleteUser('${item.id}')"> 删除 </button>
                <button onclick="getUser('${item.id}')"> 前往修改 </button>
              <div>`
              userList.appendChild(newDiv)
            })
          })
        }

        function getUser(id){
          var query = `query GetUser($id: ID){
             getUser(id: $id){
               id
               name
               sex
               age
             }
          }`

          request({
            query,
            variables: {id}
          })
          .then(r => r.json())
          .then(({data})=>{
            const user = data?.getUser;
            const updateName = document.getElementById('updateName')
            const updateSex = document.getElementById('updateSex')
            const updateAge = document.getElementById('updateAge')
            const updateId = document.getElementById('updateId')
            updateName.value = user.name
            updateSex.value = user.sex
            updateAge.value = user.age
            updateId.value = user.id

             console.log(user)
          })

        }

        function updateUser(){
          const  forms = document.getElementById("updateForm");
          const user = {};
    
          for (var i = 0; i < forms.elements.length; i++) {
          	var input = forms.elements[i];
            if(input.name === 'age'){
              user[input.name] = Number(input.value)
            }else{
              user[input.name] = String(input.value)
            } 
          }

          console.log(user, 8989)
 
          var query = `mutation UpdateUser($id: ID, $name: String, $sex: String, $age: Int){
            updateUser(id: $id, name: $name, sex: $sex, age: $age){ 
              id
              name
              sex
              age
            }
          }`

          request({
            query,
            variables: user
          })
          .then(r => r.json())
          .then((data)=>{
            getData()
            alert('修改成功!')
          })

        }

        function deleteUser(id){
          var query = `mutation DeleteUser($id: ID){
            deleteUser(id: $id){ 
              id
              name
              sex
              age
            }
          }`

          request({
            query,
            variables: {id}
          })
          .then(r => r.json())
          .then((data)=>{
            getData()
            alert('删除成功!')
          })
        }
    </script>
</body>

</html>

利用live-server启动http服务

测试

image.png

总结

该案例包含了 graphql的所有基础用法,如果你要做apollo-Client和apollo-server请先熟练操作上面增删改查的案例。