# GraphQL 使用总结
GraphQL 是一种 API 查询语言,也是一种用于实现数据查询的运行时,他和传统的 Restful 方式不一样,后端要返回的数据可以由前端来进行控制。
- GraphQL 的历史
- GraphQL 核心知识
- GraphQL 小实战
# GraphQL 的历史
在很长一段时间,包括现在,大多数的接口还是采用的 RestFul 接口方式,这非常符合我们的思维方式,RestFul 通过将接口分成不同的 controller、action 等方式来将接口进行归纳,通过制定一套标准的接口返回格式来规范前后端之间的交互。
我们仔细思考一下,RestFul 其实更多的是通过前后端的“约定”来管理接口,如果程序员不按照这个约定呢?可能就会出问题,常见的问题分为以下几类:
- 数据过量获取
- 数据获取不足
- 接口太多难以管理
- 接口返回值不按规范进行
- 一个页面需要调用多个接口才能获取全部的数据
已上几个问题相信大家也会经常遇见,虽然处理很简单,但沟通成本实在是太大,如果前端只是发现一个字段名不正确,也需要后端配合修改发布上线,才能解决。
好在 GraphQL 看到了 RestFul 的痛点,专注于接口查询,制定了一套自己的接口规范,让客户端可以根据需要,自己定制化需要返回的数据。
举一个简单的例子,我们发一个 get 请求,只要提供 query 参数,就可以根据提供的参数得到对应的数据格式。
// 请求 header
query {
person(personId:7){
name,
age
}
}
// 响应
// {
// name:'mengsixing',
// age:26
// }
2
3
4
5
6
7
8
9
10
11
12
# GraphQL 核心知识
GraphQL 中最重要的概念就是 Schema,Schema 表示的是接口的类型,也就是前后端接口的一个约定,这个约定通常是写在一个单独文件中,也即是 .graphql
的文件。
定义好 Schema 之后,前后端就会严格按照这个 Schema 进行请求和响应。
例如:如果我们的数据库中有一个学生表,我们可以定义一个对象类型去表示这个表中的字段,除此之外,我们可以扩展一些方法(根据学生的分数判断是否优秀)。
除此之外,在 GraphQL 中还包含有以下几种类型。
# 内置标量类型:Int、Float、String、Boolean、ID
# 自定义标量类型
type Student {
name: String
age: Int
sex: Boolean
isGood(grade):Boolean
}
# 自定义标量类型
type Photo {
url:String
size:Int
type:PhotoType
postedBy:Student
}
# 枚举类型
enum PhotoType {
SELFLE
ACTION
}
# 查询类型
type Query {
allStudents:[Student]
allPhotos:[Photo]
}
# 变更类型
type Mutation {
postPhoto(input: PostPhotoInput): Photo
}
# 输入类型
input PostPhotoInput {
url:String
}
# 订阅类型
type Subscription {
newPhoto:Photo
newStudent:Student
}
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
在上述的类型中,有 3 个比较重要的类型,他们分别是:Query、Mutation、Subscrition,只有它们才能作为每一个 GraphQL 查询的入口(也就是 get 请求上携带的参数)。
# Query
一个 query 即表示一次查询,同时也可以支持嵌套。
例如:我们发送 allPhotos 去查询所有的照片,同时嵌套了一层 postedBy 查询,就可以找到照片的上传者。
query {
allPhotos {
url
postedBy {
name
}
}
}
2
3
4
5
6
7
8
后端在接收到客户端的请求之后,使用对应的 Resolver 返回对应的所有照片,然后根据 postedBy 中存放的学生 id,找到对应的学生信息,然后一并进行返回,对应的配置如下。
const students = [];
const photos = [];
const resolver = {
Query: {
allPhotos: () => {
return photos;
}
},
Photo: {
postedBy: photo => {
return students.find(item => item.id === photo.postedBy);
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# Mutation
GraphQL 除了强大的查询系统之外,还提供了 Mutation 变更,其实就是告诉服务器,想要修改数据,类似于 RestFul 中的 put。
例如,上传一个图片,并返回上传好的图片 size,需要发送如下 header。
mutation {
postPhoto(url:'https://baidu.com/defaul.png'){
url,
size
}
}
2
3
4
5
6
如果是上传多个参数,需要拼接非常多的字符串操作,Mutation 给我们提供了简便方法,即通过配合输入类型,让传参更加方便。
例如:上传数据时的查询体中只发送占位符,上传的内容单独放在 variables 字段中。
mutation postPhoto($input: PostPhotoInput){
postPhoto(url:$input.url){
url,
size
}
}
// 参数单独存放
// http://xx.com/graphql?variables={input:{url:"https://baidu.com/defaul.png"}}
2
3
4
5
6
7
8
# Subscrition
Subscrition 和其他类型类似,不同点在于它表示订阅,也就是说,如果在 Subscrition 中定义了查询内容,就通过 webSocket 和前后端建立了一个双向数据通道,每当数据有变动,就可以自动向客户端推送。
例如:上面我们已经定义了 Subscrition 类型,接下来我们定义一个 action 类型的照片,当新的 action 类型的照片插入时,则会通知其他查询刷新数据。
subscription{
newPhoto(type:'ACTION'){
url
size
}
}
2
3
4
5
6
对应的 Resolver 配置如下。
const resolver = {
Mutation: {
postPhoto: async (parent, args, { pubsub }) => {
// 上传图片时触发更新
pubsub.publish("photo-add");
}
},
Subscription: {
newPhoto: {
// 监听 photo-add 事件,并实时向客户端推送
subscribe: (parent, args, { pubsub }) => {
return pubsub.asyncIterator("photo-add");
}
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# GraphQL 小实战
接下来我们通过 Apollo 和 Express 来搭建一个 GraphQL 服务器端。
步骤如下:
- 定义 GraphQL 类型
- 使用 Apollo 启动一个服务
- 编写处理 GraphQL 类型的方法:Resolver
# 首先定义 GraphQL 类型
第一步就是定义 GraphQL 类型,往往是前后端一起商量之后的结果,定义好类型之后,双方就可以根据这个类型并行开发,互不影响。
# 自定义标量类型
type Student {
name: String
age: Int
sex: Boolean
grade: Int
isGood: Boolean
}
# 自定义标量类型
type Photo {
url: String
size: Int
type: PhotoType
postedBy: Student
}
# 枚举类型
enum PhotoType {
SELFLE # 自拍类型
ACTION # 动作类型
}
# 查询类型
type Query {
allStudents(id: Int): [Student]
allPhotos: [Photo]
}
# 变更类型
type Mutation {
postPhoto(input: PostPhotoInput): Photo
}
# 输入类型
input PostPhotoInput {
url: String
}
# 订阅类型
type Subscription {
newPhoto: Photo
}
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
# 启动一个 GraphQL 服务
接下来我们启动一个支持 GraphQL 的 web 服务。
我们使用 apollo-server-express 可以非常方便的创建一个 graphql 环境。
const express = require("express");
const { ApolloServer, PubSub } = require("apollo-server-express");
const server = new ApolloServer();
const app = express();
server.applyMiddleware({ app });
httpServer.listen({ port: 4000 }, () =>
console.log(`Server ready at http://localhost:4000${server.graphqlPath}`)
);
2
3
4
5
6
7
8
# 编写 Resolver 处理 GraphQL 类型
接下来编写 Resolver 处理 GraphQL 类型对应的业务逻辑,并返回对应类型的数据。
const students = [];
const photos = [];
// 匹配处理方法
const resolvers = {
Query: {
// 第一个参数为父查询集,因为可能是在嵌套调用
// 第二个参数为查询集传的参数
// 第三个参数是在初始化 ApolloServer 时注入的对象
allStudents: (parent, args, yy) => {
return students;
},
allPhotos: () => {
return photos;
}
},
Mutation: {
// 第一个参数为父查询集,因为可能是在嵌套调用
// 第二个参数为查询集传的参数
// 第三个参数是在初始化 ApolloServer 时注入的对象
postPhoto: (parent, args, { pubsub }) => {
return photos[0];
}
},
Student: {
isGood: parent => {
return parent.grade > 90;
}
},
Photo: {
postedBy: photo => {
return students.find(item => item.id === photo.postedBy);
}
}
};
const typeDefs = fs.readFileSync("./typeDefs.graphql", {
encoding: "utf-8"
});
const server = new ApolloServer({
typeDefs, // 我们定义的 type 文件
resolvers // type 对应的处理方法
});
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
编写好这三步之后,我们的一个 GraphQL Api 就已经完成了,是不是很简单。完整的代码可以在下方链接中找到。