如果你要学习 Apollo Client 和 Apollo Server就请先学会GraphQL,因为 Apollo Client 和 Apollo Server是GraphQL不同场景下的封装,目前关于 Apollo Client 和 Apollo Server的文章特别少,希望本文能对你有一点帮助。
一.介绍
GraphQL是Facebook开发的一种数据查询语言,它于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
项目目录如下:
4.服务端代码
说明:graphql官网里面也有一个express的例子,我没有跑起来,你们可以跑起来,因为时间原因,我选择了放弃,使用自己熟悉的包体验了一把graphql,你们可以自己试试官网的案例,原理雷同。
在新项目里面创建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数据库等,为了方便学习,我直接将数据类型都定死了。
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也行。
测试
代码解释:
前端就是要写个gql,然后把gql放到http请求里面去获取数据。
三.上述总结
graphql的使用有三部分组成:
node 那边 graphqlHTTP利用schema和root创建一个graphql服务出来。
前端向graphql服务发起 post 请求,然后将gql语句以参数的形式传给graphql服务即可。
四.类型
4.1基础类型和对象类型
对graphql而言,他的类型很简单,基础类型就五种:GraphQL 结构语言支持 String、Int、Float、Boolean 和 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。
4.1.2.如果你的数据返回值是对象或者集合,他们的gql写法是一样的。
4.1.3.如果你要给query传递参数的话,请用$和variables,而不是将值拼接到字符串模版里面去。
4.1.4.传参时候的gql的写法和其他不一样你要注意下:
没有参数的时候,query后面是一个对象,传参数的时候query后面是一个函数,在这个函数里面就要把带$的参数定义好。
4.3 变更输入类型
在graphql服务的schema里面定义了很多类型,这些类型都是以type Query开头的,当我们定义它是一个对象的时候,以type List开头。一般我们会将 root 里面定义的数据的类型都放到Query里面去。如图:
但是从业务来讲,我们系统的每个模块都是由基础的增,删,改,查组成,为了区分增删改查的功能,graphql对Query做了划分,有:Mutation,Query 两类,你可以把删除,修改,新增放到Mutation里面,你可以把查找放到Query里面。
关键字input和type,input是用来定义输入类型的,新增和修改的输入类型一样,为了和其他参数类型做分区,你就可以使用input定义。不做强制,input定义的类型可以用type替换。
为了简单,你可以全部使用type。
gql使用的时候就将原来的query换成mutation就好了
五. 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服务
测试
总结
该案例包含了 graphql的所有基础用法,如果你要做apollo-Client和apollo-server请先熟练操作上面增删改查的案例。