GraphQL和restful简介

  • GraphQL是数据查询语言,是Restful API的替代品

  • GraphQL 是 API 查询语言,不是数据库查询语言,它跟 REST 是同一层次的东西,没有筛选/排序/分组/聚合这类语法,这些都是需要我们自己实现的。

  • 特点:

    • 请求需要的数据不多不少。

    • 获取多个资源,只用一个请求。

      比如请求接口A,获取到字段B,然后根据字段B,去请求接口C,获取到字段D,再根据字段D,去请求接口E。

      这样的依赖关系,可以使用GraphQL一次解决。

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

  • restful:Representational State Transfer表示属性状态转移。本质上就是用定义uri,通过API接口来取得资源。通用系统架构,不受语言限制。

GraphQL和restful对比

  • restful一个接口只能返回一个资源,graphQL一次可以获取多个资源
  • restful用不同的url来区分资源,graphQL用类型区分资源。

GraphQL Sever使用

简单使用

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
var express = require("express")
var graphqlHTTP = require("express-graphql")
var { buildSchema } = require("graphql")

// buildSchema:对外公开的查询方法和查询类型
// 这里定义了一个查询方法hello,返回值为String
var schema = buildSchema(`
type Query {
hello:String
accountName:String
}
`);

// 定义查询方法对应的处理函数
var root = {
hello: ()=>{
return "hello world"
},
accountName: ()=>{
return "hyl"
}
};

var app = express();

// 把graphql地址的请求,转发给graphqlHTTP进行处理
app.use('/myurl',graphqlHTTP({
schema:schema,
rootValue:root,
graphiql:true, // 是否启用调试界面
}))

app.listen(4000);

我们就可以进行请求了:

1
2
3
4
query {
hello
accountName
}

返回值:

1
2
3
4
5
6
{
"data":{
"hello":"hello world",
"accountName":"hyl",
}
}

进阶使用

返回嵌套的数据和自定义参数

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
var schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
}

type person {
year: Int
salary(city: String): Int
}

type Query {
account: Account
getPerson(city: String): Person
}
`);


var root = {
hello: ()=>{
return "hello world"
},
account: ()=>{
name: "hyl",
age: 22,
sex: "man",
department: "backend-engineer",
}
getPerson: ({city})=>{
const year = 80
const salary = ({city})=>{
if(city==="beijing"){
return 10000
} else {
retrun 8000
}
}
return {
"year": year,
"salary": salary
}
}
};

调用如下:

1
2
3
4
5
6
7
query {
account
getPerson(city: "北京") {
year
salary
}
}

基本参数类型

  • String
  • Int
  • Float
  • Boolean
  • ID
  1. [类型]:表示数组,eg:[Int]
  2. !:表示参数不能为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var schema = buildSchema(`
type Query {
getClassMates(classNo Int!):[String]
}
`);

var root = {
getClassMates: ({classNo})=>{
const obj = {
123:["hyl","czj","gzr","dsz"],
456:["hds","lzy","ljw","ljh"]
}
return obj[classNo]
}
};

调用如下:

1
2
3
4
query {
getClassMates(classNo: 123)
getClassMates(classNo: null)
}

GraphQL Client使用

在客户端访问myurl接口

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
var username = 3
var city = "深圳"

var query = `
query Account($username: Int!, $city: String){
account(username: $username){
name
age
sex
salary(city: $city)
}
}`;


fetch('myurl',{
method: 'POST',
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify({
query,
variables: { username:username, city:ciry }
})
})
.then(r => r.json())
.then(data => console.log('data returned:', data));

修改数据Mutations

  • 查询使用Query,修改数据使用Mutation
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
var schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
}

input AccountInput {
name: String
age: Int
sex: String
department: String
}

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

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

const FakeDB = {};

var root = {
createAccount({input}){
// 模拟数据库的保存
FakeDB[input.name] = input;
// 返回保存结果
return FakeDB[input.name]
},

updateAccount({id, input}){
// 模拟数据库的更新
const updateAccount = Object.assign({}, FakeDB[id], input)
Fake[id] = updateAccount
// 返回保存结果
return FakeDB[input.name]
}

accounts(){
var arr = []
for(const key in FakeDB) {
arr.push(FakeDB[key])
}
return arr
}
};

调用如下:

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
mutation {
createAccount{input:{
name: "hyl",
age: 22,
sex: "man",
department: "backend-engieer"
}} {
name
age
sex
department
}
}

mutation {
updateAccount(id: "hyl", input: {
age: 101
}) {
age
}
}

query {
accounts {
name
}
}

认证和中间件

  • 中间件的本质就是一个function,在接口执行之前就拦截请求,然后在决定是否能接着往下走
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
var express = require("express")
var graphqlHTTP = require("express-graphql")
var { buildSchema } = require("graphql")

var schema = buildSchema(`
type Query {
hello:String
}
`);

var root = {
hello: ()=>{ "hello world" }
};

var app = express();

// 定义中间件function
const middleware = (req, res, next) => {
if(req.url.indexOf('myurl') !== -1 && req.headers.cookie.indexOf('auth') === -1) {
res.send(JSON.stringfy({
error: "您没有权限访问这个接口"
}))
return;
}
next();
}

// 注册中间件
app.use(middleware);

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

app.listen(4000);

Constructing Types

  • 使用GraphQLObjectType定义type(类型)
1
2
3
4
5
6
7
8
9
10
11
12
const schema = buildschema{`
type Account {
name: String
age: Int
sex: String
department: String
}

type Query {
account(username: String): 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
// 1.将type Account字符串 修改成一个 GraphQLObjectType类型
var AccountType = new graphql.GraphQLObjectType({
name: "Account",
fields: {
name: {type: graphql.GraphQLString},
age: {type: graphql.GraphQLInt},
sex: {type: graphql.GraphQLString},
department: {type: graphql.GraphQLString}
}
});

// 2.将type Query字符串 修改成一个 GraphQLObjectType类型
var queryType = new graphql.GraphQLObjectType({
name: "Query",
fields: {
account: { // account: 函数名叫account
type: AccountType, // type: 函数返回值是AccountType
args: { // args: 函数传入的参数
username: {type: graphql.GraphQLString }
},
resolve: function(_, {username}) { // resolve: 函数体
const name = username;
const sex = "man";
const age = 18;
const department = "backend-engineer";
return {
name,
sex,
age,
department
}
}
}
}
});

// 3.创建Schema
var schema = new graphql.GraphQLSchema({ query: queryType });