Egg.js中使用GraphQL实战

4,045 阅读4分钟

GraphQL实战

1. GrqphQL是什么

GraphQL是一种面向数据的API查询风格。

GraphQL是由Facebook主导开发的一款面向数据的API查询语言。客户端只需给他一个描述,然后GraphQL就能从数据库组合出符合描述的数据并返回。

2. GraphQL解决了什么问题

在开发中我们一般都使用的是RESTful API,它是是面向资源的。通常渲染一个页面我们需要多个资源,REST API请求多个资源时就会载入多个URL,而GraphQL可以通过一次请求就可以获取到需要的所有数据。

解决痛点:

  • 接口返回数据格式并不是调用者(前端)理想型

  • 多个资源只用一个请求,减少服务器压力

  • 数据字段由调用者控制

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

示例:

这里以掘金的首页举例,它可能分为这几个资源(文章列表,分类列表,作者榜)

REST API

文章列表/articles

[
    {
        title: 'xxx',
        content: 'xxx内容'
    }
]

分类列表/categories

[
	{
		id: '1'
		name: '前端',
	},
	{
		id: '2'
		name: '后端',
	},
]

作者推荐榜/recommendationAuthor

[
    {
        id: 1,
        username: '张三'
    }
]

上面是用REST接口来获取首页需要的数据,需要发送3次请求,再看下GraphQL的请求是什么样?

GraphQL查询接口/graphql/query

query getHomePage() {
	articleList($first: 0) {
		title
        content
    },
    categoryList(){
        id
        name
    },
    recommendationAuthorList($limit: 10) {
        id
        username
    },
      
} 

通过Graphql只用请求一次就可以拿到全部信息,返回的数据格式都是由调用者控制的,不多不少。

3 GraphQL重要概念

这里先介绍几个GraphQL比较重要的概念,可以参考着GraphQL官网文档看。

3.1 操作类型

操作类型可以是query、mutation、substription ,描述你打算做什么类型的操作。

  1. query 查询
  2. mutation 变更
  3. substription 订阅,当数据更新后,会推送消息

每一个Graphql都有一个query类型,也可能有一个mutation类型。他们定义了每一个Graphql查询的入口。

3.2 标量类型

  • Int 有符号32位整数
  • Float 有符号双精度浮点数
  • String UTF-8字符序列
  • Boolean true或者false
  • ID ID标量类型标识一个唯一标识符

3.3 对象类型和字段

type Article{
	title: String
	author: [User]
}

上面我声明了一个GraphQL对象类型,这里声明了两个字段并指定了字段类型。

String是内置的标量类型之一,查询中无法对他进行次级选择。

[User] 表示一个User数组,数组中的每个项目都是一个User对象。

  • User对象怎么来的呢?

    如Article一样 使用type关键字声明

    type User {
    	name: String
    	age: Int
    }
    
  • 声明对象有什么用?

    通过Query对外提供查询入口时,我们通常都是以一个对象为一个单位。可以把每个对象看作成一个接口。

3.3 输入类型

通过Mutation对外提供变更入口。有时你需要传递一整个对象作为新建对象,之前的标量类型就不能满足了。

官网示例:

4. egg-graphql实战

客户端使用前,我们需要在服务端定义Schema结构。

4.1 使用脚手架生成Egg项目

PS: 下载太慢时可以先全局安装egg-init脚手架,再执行egg-init --type simple --registry china使用淘宝镜像生成项目

4.2 安装graphql插件

  1. 执行npm i egg-apollo-server --save

  2. 在egg项目中添加graphql的相关配置, 详见egg-apollo-server

​ 这里我没有用egg-graphql插件而使用的是egg-apollo-server

PS:config.graphql.subscriptions用来对grapql请求做鉴权的,在此demo里不需要改为false即可

4.3 定义schema

├── app
│   ├── controller
│   │   └── home.js
│   ├── graphql
│   │   ├── article
│   │   │   ├── resolver.js
│   │   │   └── schema.graphql
│   ├── router.js
│   └── service
├── config
│   ├── config.default.js
│   └── plugin.js
├── package.json
└── package-lock.json
  • schema.graphql

    定义GraphQL类型和操作

    extend type Query {
        articleList(first: ID): [Article]
    }
    
    type Article {
        id: ID
        title: String
        content: String
        author: Author
    }
    type Author {
        name: String
        age: Int
    }
    
    extend type Mutation {
        addArticle(title: String, content: String, author: AddAuthor): Article
    }
    
    input AddArticle {
        title: String
        content: String
    }
    
    input AddAuthor {
        name: String
        age: Int
    }
    
  • resolver.js

    实现操作

    'use strict';
    const list = [
      {
        id: 1,
        content: 'aaa',
        title: '',
        author: {
          name: 'aaa',
          age: 18,
        },
      },
      {
        id: 2,
        content: 'bbb',
        title: '',
        author: {
          name: 'aaa',
          age: 18,
        },
      },
    ];
    module.exports = {
      Query: {
        articleList: () => {
          return list;
        },
    
      },
      Mutation: {
        addArticle(root, params, ctx) {
          console.log(params);
          params.id = list.length++;
          list.push(params);
          return params;
        },
      },
    };
    
    

4.4 graphql调试

浏览器打开 http://127.0.0.1:7001/graphql 进行调试

PS: 测试前需要关闭csrf保护,否则调试发送的post请求将会被拦截

左侧为查询区,在这里写操作语句。右侧为执行结果。

查询调试

{
  articleList(first: 0){
    id
    title
    content
    author{
			name
      age
    }
	}
}

变更调试

mutation {
  addArticle(title: "title", content: "content", author: {name: "张三", age: 15}){
    title
    content
    id
  }
}