# GraphQL 特点

  1. 精确请求需要的数据,不多不少。例如: account 中有 name, sex, age 可以只取得需要的字段 name。
  2. 可以只用一个请求获取多个资源。
  3. 描述所有可能的类型系统,方便维护,可以根据需求平滑演进,添加或隐藏字段。

# GraphQL 与 restful 区别

  1. restful 一个接口只能返回一个资源,graphql 一次可以获得多个资源
  2. restful 使用不同 url 来区分资源,graphql 使用类型区分资源。

# GraphQL 参数类型

基本类型: String, Int, Float, Boolean, ID;可以在 schema 声明的时候直接使用。

数组类型: [类型] 代表数组,例如 [Int] 代表整型数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// hello.js
const express = require("express");
const graphqlHTTP = require("express-graphql");
const { buildSchema } = require("graphql");

// 构建 schema, 定义查询语句和类型
const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
}
type Query {
hello: String
age: Int
account: Account
}
`);

// 定义查询对应的 resolver, 也就是查询对应的处理器
const root = {
hello: () => {
return "Hello, World!";
},
age: () => {
return 18;
},
account: () => {
return {
name: "桔梗",
age: 18,
sex: "女",
department: "技术部",
};
},
};

const app = express();

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

app.listen(3000, () => {
console.log("listening on 3000...");
});

启动 hello.js node hello.js , 在浏览器输入 http://localhost:3000/graphql, 在 GraphiQL 调试面板输入 query 语句, 中间面板会返回查询语句

1
2
3
4
5
6
7
8
9
query {
hello
age
account {
name
age
sex
}
}

# GraphQL 参数和返回值

  1. 和 js 一样,小括号内定义形参,参数需要定义类型。
  2. ! 表示参数不能为空。
  3. 返回需要定义返回类型
1
2
3
type Query {
rollDice(numDice: Int!, numSides: Int): [Int]
}

# GrphQL clients

base.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const express = require("express");
const graphqlHTTP = require("express-graphql");
const { buildSchema } = require("graphql");

// 构建 schema, 定义查询语句和类型
const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
}
type Query {
getClassMates(classNo: Int!): [String]
account(username: String): Account
}
`);

// 定义查询对应的 resolver, 也就是查询对应的处理器
const root = {
// {classNo} 从 arguments 中解构出 classNo
getClassMates({ classNo }) {
const obj = {
1: ["java", "scala", "groovy"],
2: ["python", "ruby", "php"],
3: ["oracle", "mysql", "mongo"],
};
return obj[classNo];
},
account({ username }) {
const name = username;
const sex = "男";
const age = 18;
const department = "开发部";
const salary = ({ city }) => {
if (city === "北京" || city === "上海") return 10000;
return 3000;
};
return {
name,
sex,
age,
department,
salary,
};
},
};

const app = express();

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

// 公共文件夹,供用户访问静态资源
app.use(express.static("public"));

app.listen(3000, () => {
console.log("listening on 3000...");
});

public/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>client</title>
</head>

<body>
<button onclick="getData()">获取数据</button>
<script>
function getData() {
const query = `
query Account($username: String, $city: String){
account(username: $username) {
name
age
sex
department
salary(city: $city)
}
}
`;
const variables = {
username: "犬夜叉",
city: "北京",
};

fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
query: query,
variables: variables,
}),
})
.then((res) => res.json())
.then((json) => {
console.log(json);
});
}
</script>
</body>
</html>

启动 base.js, node base.js

# GraphQL + MySql

query 查询数据, Mutations 修改数据

accouont 表结构如下:

1
2
3
4
5
6
7
8
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` varchar(45) DEFAULT NULL,
`department` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='graphql test table'

db.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
const express = require("express");
const graphqlHTTP = require("express-graphql");
const { buildSchema } = require("graphql");
const mysql = require("mysql");
const connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "root",
database: "test",
});

// 构建 schema, 定义查询语句和类型
const schema = buildSchema(`
input AccountInput {
name: String
age: Int
sex: String
department: String
}

type Account {
name: String
age: Int
sex: String
department: String
}

type Mutation {
createAccount(input: AccountInput): Account
updateAccount(id: ID!, input: AccountInput): Account
deleteAccount(id: ID!): Boolean
}

type Query {
accounts: [Account]
}
`);

// 定义查询对应的 resolver, 也就是查询对应的处理器
const root = {
createAccount({ input }) {
// 数据保存
const data = {
name: input.name,
sex: input.sex,
age: input.age,
department: input.department,
};
return new Promise((resolve, reject) => {
connection.query(
"INSERT INTO account SET ?",
data,
function (error) {
if (error) {
console.log("error: " + error.message);
return;
}
}
);
// 返回结果
resolve(data);
});
},

deleteAccount({ id }) {
return new Promise((resolve, reject) => {
connection.query(
"delete from account where name = ?",
id,
function (error) {
if (error) {
console.log("error: " + error.message);
reject(false);
return;
}
}
);
// 删除成功
resolve(true);
});
},

updateAccount({ id, input }) {
// 数据更新
// const data = {
// name: input.name,
// sex: input.sex,
// age: input.age,
// department: input.department
// }
// 如果只更新 department, 其它属性就为空,用上面方式会将 name 等都更新为 null
// 直接让 data=input, 就不会动到其它属性
// # mutation {
// # updateAccount(id: "虞姬", input: {
// # department: "程序猿"
// # }) {
// # department
// # }
// # }
const data = input;
return new Promise((resolve, reject) => {
connection.query(
"update account set ? where name = ?",
[data, id],
function (error) {
if (error) {
console.log("error: " + error.message);
return;
}
}
);
// 返回更新后的数据
resolve(data);
});
},

accounts() {
return new Promise((resolve, reject) => {
connection.query(
"select name, age, sex, department from account",
(error, results) => {
if (error) {
console.log("error: " + error.message);
return;
}
const arr = [];
for (let i = 0; i < results.length; i++) {
arr.push({
name: results[i].name,
age: results[i].age,
sex: results[i].sex,
department: results[i].department,
});
}
resolve(arr);
}
);
});
},
};

const app = express();

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

app.listen(3000, () => {
console.log("listening on 3000...");
});

GraphQL crud

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// create
mutation {
createAccount(input: {
name: "虞姬"
age: 20
sex: "女"
department: "程序媛"
}){
name
}
}


mutation {
createAccount(input: {
name: "aaa"
age: 20
sex: "女"
department: "程序媛"
}){
name
}
}


// delete
mutation {
deleteAccount(id: "aaa")
}


// update
mutation {
updateAccount(id: "虞姬", input: {
department: "程序猿"
}) {
department
}
}


// retrieve
query {
accounts {
name
age
sex
department
}
}
Edited on