GraphQL 入门

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

Why GraphQL

GraphQL 的出现主要是针对于传统的 REST API。传统的 REST API 使用资源实体的概念来划分各个数据实体,当我们需要请求数据的时候,我们往往需要发送多个请求来获取所需要的数据,一般来说,实体当中往往与其它不同类型的实体有着各样的关系。

例如我们需要展示各个用户所编写的博客文章,这需要获取博客的数据以及用户的数据。如果我们通过 REST API 获取,我们首先需要通过 GET /api/posts 获取所有的文章,返回的数据中就包含了文章的相关信息,如标题,内容,创建时间,以及作者的 id。根据获取的作者 id,我们需要根据每一个用户 id,发送请求 GET /api/users/:id 获取每个作者的信息。所以这样单一个展示页面,我们就可能需要发送大量的请求来获取数据。如果这个情况发生在需要数据现场渲染页面的网站,就比较尴尬,尤其是当网络情况较差时。

而使用 GraphQL 的话,只需要单个 Query 即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
query {
    posts {
        title,
        content,
        tags,
        date,
        user {
        	username,
          avatar,
          catchphrase,
          favorite_dog
        }
    }
}

另外传统的 API 在前后端进行接口交互时,往往需要文档来进行沟通交流,通过文档上面的定义和描述,来告诉前端接口到底长什么样,可以获取怎样的数据,以及怎样获取数据。然而文档的质量参差不齐,什么经常存在信息缺失的情况,尤其是需要人工编写的接口描述~~(给你写个类型信息和返回信息都不愿意)~~。尽管有着 Swagger 这些接口文档自动生成的工具,但是还是需要后端开发者付出额外的精力来添加描述信息。而如果写 GraphQL 的话,写一个 Scheme 就可以把支持的类型及其属性都清楚地暴露出去,只要再完善一下其中的 Query 以及 Mutation 内容,即可以把 GraphQL 支持的查询和更改操作都清楚地暴露出来。这些内容都可以通过算是内置的辅助工具 GraphiQL,进行文档的查询,以及实际的尝试。

How

to build

如何编写一个 GrahpQL 服务呢?我们首先需要定义好它的 Schema,包括其定义的类型(Type),输入类型(Input)。在 GraphQL 里面,服务能够支持的查询和更改操作,也是类型,分别是 Query 和 Mutation。

  • Type: 每个类型由多个属性组成,每个属性包含一个名字,以及该属性字段的类型,该类型可以为基本类型(Int, String, Boolean, Float, ID),数组,或者是自定义的对象类型
  • Input: 输入类型也是一个 Type
  • Query: 表示服务支持的查询内容,每个属性表示某个 query,其类型则为返回类型,可以接收参数
  • Mutation: 表示服务支持的更改操作,每个属性表示操作名,需要接收输入类型的参数,其类型为返回类型

以下为一个关于 todo 的 GraphQL Schema

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type TodoItem {
    id: Int!
    title: String!
    content: String
    checked: Boolean!
    createdTime: String!
}

type Query {
    todoItems: [TodoItem]
    todoItem(id: Int!): TodoItem
}

input TodoItemInput {
  title: String!
  content: String
  checked: Boolean!
  createdTime: String!
}

type Mutation {
    createTodoItem(todo: TodoItemInput!): TodoItem
    updateTodoItem(id: Int!, todo: TodoItemInput!): TodoItem
}

to use

怎么使用 GraphQL 服务呢?

Query

需要查询数据时,我们只需要通过花括号把需要的类型和字段的列举出来即可。当需要传入变量,我们可以在 query 处进行定义,然后在变量处进行值传递。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
query {
  TodoItem {
    title
    content
  }
}

# or

query ($id: Int!) {
  todoItem(id: $id) {
    id
    title
  }
}

# variables
{
  "id": 1
}

Mutation

进行数据更改时,也是类似,传入变量,调用相应的 mutation 方法即可,另外根据需要,我们还可以选择返回的属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mutation ($todo:TodoItemInput!){
  createTodoItem(todo:$todo) {
    id
    title
    content
  }
}

# variables
{
  "todo": {
  	"title": "asdafa",
    "content": "asdaf",
    "createdTime": "today",
    "checked": false
  }
}

to implement

怎样快速搭一个 GraphQL 服务出来呢?当然是采用动态语言最快了,而其中,又自然是选择使用 GraphQL 最多的 JavaScript 语言实现起来最快。

这里我们使用 express 来提高 http 服务,使用 express-graphql 来搭建 GraphQL 服务

1
npm install --save express express-graphql graphql

然后就是一把梭地实现,首先构建 GraphQL Schema,然后搭建 http 服务,构建并绑定一个 GraphQL handler,启动服务,完。

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

var schema = buildSchema(``) // 内容省略

// 那个数组假装一下数据库
const todoItemDb = []

var root = {// 这里用来存储各个 query 和 mutation 的响应函数
  todoItems: () => todoItemDb,
  todoItem: ({id}) => {
    for(let todo of todoItemDb) {
        if(todo['id'] == id) return todo
    }

    return {}
  },
  createTodoItem: ({todo}) => {
      todo['id'] = todoItemDb.length
      todoItemDb.push(todo)
      console.log(todo)
      return todo
  },
  updateTodoItem: ({id, todo}) => {
    for(let i=0;i<todoItemDb.length;i++) {
        if(todoItemDb[i]['id']== id) {
            todo['id'] = id
            todoItemDb[i] = todo
            return todo
        }
    }
  }
}

var app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);

实现就这么简单 2333

后记

在写这篇博客的时候,其实写到后面有点点迷茫,所谓的 GraphQL 真的是这么好吗?如果真的这么好,为什么当前主流的前后端分离,后端接口的方式还是用传统方式的类 RESTful API 的方式来设计和实现呢?

其实回过头看上面举出的例子,那些所谓的需要多次请求才获取到的数据,其实本质上也是因为后端考虑要获取多个数据,才将其整合在一起,作为一个新的独立的 endpoint 来暴露出来的。换句话,其实传统的 API 也可以做到,只需要再写一个 endpoint,专门为这个情况设置独立的 handler。

不过这种专门设置一个新的 endpoint 也未免太过不够 elegant,而且 GraphQL 对我这种还没经历过实际项目的新手来说,最大的优点,还是其自带的 GraphiQL 吧,一个带 ui 的 playground 提供给使用者来了解 API 中支持的类型,查询和变更操作,并且可以随时进行尝试,实在吸引。

所以以后如果需要到 GitHub 挖点数据的时候,不妨把请求方式从其传统的 REST API 上搬到 GraphQL 当中。这样就不需要再在命令行或者是 Postman 的方式来发请求尝试,直接在网页端界面解决。