GraphQL
GraphQL是Facebook开发的一种数据查询语言,它于2015年公开发布。
GraphQL API的出现是为了解决传统RESTful API在某些场景下的一些限制和挑战。
下面是一些导致GraphQL API出现的主要原因:
- 减少多次请求:传统的RESTful API一次请求只能返回一个资源,而GraphQL API一次可以返回多个资源,因此可以减少多次请求的次数。
- 避免过度获取和欠获取:传统的RESTful API通常以预定义的数据结构返回数据,导致客户端要么获取了过多的数据,要么获取了不足的数据。GraphQL API通过让客户端指定所需的数据结构,避免了这种问题,客户端可以精确地获取所需的字段和关联数据,避免了数据的浪费和不足。
由于restful api预定义好了每个接口的返回的数据,所以即使我只想要用户的name,却还是会返回用户的整个字段内容给我。而graphql查询,如果我只要用户的name,那么就只会返回name给我,不会返回整个用户字段给到用户,避免了数据的过度获取。
graphql是一种数据查询语言,GraphQL的API通常是一个URL对应多种查询,不同的查询是通过请求体来区分的。
Hello World
运行 GraphQL API 服务器的最简单方法是使用 Express
- 初始化一个project
npm init -y
-
安装依赖
1
npm install express graphql-http graphql --save
-
搭建一个express server
1 2 3 4 5 6 7 8 9
var express = require('express'); var app = express(); app.use('/', (req, res) => { res.send('Hello World'); }) app.listen(3001, ()=>{ console.log('Server running on port 3001'); })
-
添加graphQL
1 2
var { buildSchema } = require('graphql'); const { createHandler } = require('graphql-http/lib/use/express');
在 GraphQL 中,schema 定义了客户端可以查询的数据类型以及它们之间的关系。使用 buildSchema 函数可以创建 schema 对象。这个 schema 对象可以被传递给 GraphQL 的执行器,以便它可以执行来自客户端的查询,并返回所需的数据。
1
2
3
4
5
6
7
// Construct a schema, using Graphql schema language. 1
// The parameter of buildSchema function is a String type. 2
var schema = buildSchema(`
type Query {
hello : String!
}
`);
这段schema 定义了所有可能的类型和操作,并且充当客户端和服务器之间的契约。注意,buildSchema方法的参数这里使用的是反引号.
在这个 schema 中,我们定义了一个类型:Query
Query类型是 GraphQL schema 中的预定义类型。其中定义了一些查询操作(方法),客户端可以使用这些操作从服务器中检索数据。
在Query类型中,我们定义了一个字段:hello
。hello字段是一个字符串类型,它的值为String!
,表示这是一个非空的字符串类型。
在实际应用中,我们需要提供一个名为hello的函数,用于处理查询操作。
1
2
3
4
5
6
//The root provide a resolver function for each API endpoint 1
var root = {
hello: () => {
return 'hello world';
}
};
在 GraphQL 中,root 对象是一个用于处理客户端请求的实际对象(根对象),它包含了所有的处理函数。hello 方法是箭头函数的表示方式,返回一个字符串 ‘hello world’,这个方法用于处理 hello 查询操作。在GraphQL 中,查询操作是客户端用于从服务器检索数据的一种方式。
1
2
3
4
5
6
7
8
// Create and use the GraphQL handler.
app.all(
'/graphql',
createHandler({
schema: schema,
rootValue: root,
}),
);
-
使用以下命令运行此 GraphQL 服务器:
1
node index.js
此时,你将有一个正在运行的 GraphQL API
-
创建一个客户端index.html
-
自己写一个客户端测试graphql太麻烦了,我们需要一个现成的 GraphQL 客户端来向 API 发出 GraphQL 查询。
GraphiQL
是 GraphQL 的 IDE, 查询和探索 GraphQL API 的好方法。官方提供的方法是使用ruru
包,该包捆绑了预构建的 GraphiQL 和一些流行的增强功能。 为此,请安装模块1
npm install --save ruru
然后将以下内容添加到文件中,然后重新启动命令:node index.js
1
2
3
4
5
6
7
const { ruruHTML } = require('ruru/server');
// Serve the GraphiQL IDE.
app.get('/', (_req, res) => {
res.type('html');
res.end(ruruHTML({ endpoint: '/graphql' }));
});
导航到 http://localhost:3001,应该会看到一个允许您输入查询的界面。 在左侧输入
1
2
3
{
hello
}
点击Execute Query, 可以看到右侧返回
1
2
3
4
5
{
"data": {
"hello": "hello world"
}
}
数据类型
- 基本数据类型: String, Int, Float, Boolean, ID(ID类型的本质是字符串类型,但是如果是ID类型就表示数据不能重复,但是GraphQL本身并没有内置的机制来强制确保ID类型的数据不重复,需要协同数据库共同控制),这几个类型都可以在schema声明的时候直接使用。
- [类型]代表数组,例如[Int]代表整数数组。
默认情况下,每个类型都是可为 null 的 - 返回任何类型都是合法的。使用感叹号表示类型不能为空,不可为空的字符串string!
自定义数据类型
GraphQL中除了几种基本的数据类型,有两种预定义的数据类型,一种是Query
,另一种是Mutation
。
如果用户想自定义其它数据类型,则可以在buildSchema方法中通过type定义其它类型,例如我们想定义一个User类型和Post(博客)类型。
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
var schema = buildSchema(`
type User {
id: ID!
name: String!
posts: [ID!]
}
type Post {
id: ID!
title: String!
content: String!
author: ID!
}
type Query {
hello : String!
getUser: User!
getPost: Post!
}
`);
var root = {
hello: () => {
return 'hello world';
},
getUser() {
return {
id: '1',
name: 'John Doe',
posts: ['1', '2']
};
},
getPost() {
return {
id: '1',
title: 'Post Title',
content: 'Post Content',
author: '1'
};
}
};
在getUser和getPost方法的具体实现中,都需要返回对应的自定义数据类型,我们的返回值是字典类型,可以返回比期望的数据类型更多的键值对,因为graphQL只会匹配和期望的数据类型中相同的字段。但是不能更少(不会报错),但是这样在查询的时候对应被遗漏的字段的值就为null了,如果恰好被遗漏的字段的值不能为null,又恰好去查询了这个字段的值,那么查询就会报错。
由于getUser方法和getPost方法的返回类型都是自定义类型,在调用的时候我们必须使用{}来指定我们希望服务器端返回给我们哪几个字段的值
1
2
3
4
5
6
7
8
9
10
{
getUser{
id
name
}
getPost{
id
title
}
}
返回数据
1
2
3
4
5
6
7
8
9
10
11
12
{
"data": {
"getUser": {
"id": "1",
"name": "John Doe"
},
"getPost": {
"id": "1",
"title": "Post Title"
}
}
}
参数传递
和js传递参数一样,在小括号内定义行参,但是参数需要在schema中定义类型。
graphQL请求参数,参数的值如果是String,必须使用双引号,使用单引号会报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Query {
getUserById(id:ID!): User!
getPostById(id: ID!): Post!
}
getUserById({id}){
var User1={id:'001',name:'Alice',posts:['11','12']}
var User2={id:'002',name:'Mary',posts:['13','14']}
if(User1.id === id){
return User1
} else {
return User2;
}
},
getPostById({id}){
var Post1={id:'1',title:'What is API',content:'API is an interface ..', author:'1'}
var Post2={id:'1',title:'What is GraphQL',content:'GraphQL is an langu...', author:'1'}
if(Post1.id === id){
return Post1
} else {
return Post2;
}
}
发出以下请求
1
2
3
4
5
6
7
{
getUserById(id:"001"){
id
name
posts
}
}
得到的结果
1
2
3
4
5
6
7
8
9
10
11
12
{
"data": {
"getUserById": {
"id": "001",
"name": "Alice",
"posts": [
"11",
"12"
]
}
}
}
query和mutation
在GraphQL中,Query和Mutation是两种特殊的根操作类型,都是预定义好的,用于定义可执行的查询和变更操作。 Query类型: Query类型用于定义可以执行的读取操作。它表示你可以从服务器获取数据的能力。在前面我们一直是查询操作,所以我们所有查询操作的定义其实都写在Query type下面。
1
2
3
4
type Query {
getUser(id: ID!): User!
getPost(id: ID!): Post!
}
Mutation类型: Mutation类型用于定义可以执行的写入或修改操作。它表示你可以向服务器发送请求以更改数据的能力。通常,Mutation`类型中的字段对应于可以对服务器上的数据进行修改的操作。 假设目前我们需要创建一条Account数据。我们可以和之前一样使用type自定义Account数据类型:
1
2
3
4
5
6
type Account {
id: ID!
name: String
age: Int
salary(city: String): Int
}
但是使用type定义出来的数据类型只能作为查询类型,却不能作为请求的输入类型。所以我们还需要使用input定义输入类型AccountInput作为请求的输入参数:
1
2
3
4
5
input AccountInput {
name: String
age: Int
city: String
}
接下来我们定义mutation类型以及其createAccount操作,createAccount的输入参数是AccountInput类型:
1
2
3
type Mutation {
createAccount(input: AccountInput): Account
}
注意,由于如果只有Mutation类型而没有Query类型的话,graphQL不支持,所以我们再添加一个Query类型:
1
2
3
type Query {
getAccount(id: ID!): Account
}
完整代码:
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
var schema = buildSchema(`
type Account {
id: ID!
name: String
age: Int
salary(city: String): Int
}
input AccountInput {
name: String
age: Int
city: String
}
type Query {
getAccount(id: ID!): Account
}
type Mutation {
createAccount(input: AccountInput): Account
}
`);
var root = {
createAccount({ input }) {
const name = input.name;
const age = input.age;
// salary is a field resolver, not a direct value
return {
id: "001",
name,
age,
salary: ({ city }) => (city === 'Shenzhen' ? 2000 : 1000)
};
},
getAccount({ id }) {
let name = '';
let age = null;
if (id === '001') {
name = 'Tom';
age = 29;
}
return {
id,
name,
age,
salary: ({ city }) => (city === 'Shenzhen' ? 2000 : 1000)
};
}
};
输入以下查询
1
2
3
4
5
6
7
8
mutation{
createAccount(input:{name:"xx1", age:18, city:"Shenzhen"}){
id
name
age
salary(city:"Shenzhen")
}
}
得到的结果
1
2
3
4
5
6
7
8
9
10
{
"data": {
"createAccount": {
"id": "001",
"name": "xx1",
"age": 18,
"salary": 2000
}
}
} # Apollo ApolloServerPluginDrainHttpServer apolloServerEventHandlerPlugin apolloServerLandingPagePluging # GraphQL In TypeScript type-graphql
raphql-ws
GraphQL + Angular
CodeGen
使用GraphQL和Apollo构建Angular ToDo应用
GraphQL真香入门教程
自动生成GraphQL接口文件的步骤
GraphQL 的 正确打开方式 (apollo-client前戏)
apollo-client 和 apollo-server的正确打开方式
Apollo_Angular_GraphQl_Start
Angular Integration with GraphQL: Complete Guide to Setup and Usage
Complete guide to GraphQL in Angular