vlambda博客
学习文章列表

一文快速入门 Graphql

一张脑图总结全文:

GraphQL 与 RESTful API

GraphQL 是由 Facebook 开发并开源的。提到 GraphQL ,大家自然而然会提起 RESTful api。下面对比一下 RESTful api 和 GraphQL 的优缺点。

优点:

  1. 声明式的接口获取 RESTful api 返回的字段冗余, 当多个终端共用接口时,尤其明显。GraphQL 可精准的返回所需的数据结果,减少数据传输大小。

  2. 嵌套复杂数据仅需一次调用 RESTful 对于嵌套的复杂数据需要多次调用,而 GraphQL 只需要一次。

  3. 愉快地前后端联调效率 REST 每次新加字段,需频繁沟通,且需借助 swagger 生成接口文档, GraphQL 自动生成标准文档。

如果上面的优点看不懂,没关系,我们来举一个栗子,加速理解:

服务端 getUser 接口返回了 id, name 信息, 但是另外一个场景,需要额外多返回几个字段, 比如 email, adress 等。此时服务端要怎么做?有以下三种做法:

  1. 新开一个接口, 返回所需要的所有字段
  2. 请求增加一个 type ,用于区分场景,服务端根据不同 type 返回不同的字段
  3. 不管三七二十一, 在原有接口上增加多的字段。

如果只是 1 个,2 个场景还好,但如果后期有 n 个场景,需要返回非常多的字段,这不仅会浪费带宽,客户端数据解析也会影响响应时间,从而影响用户体验。那让后台新增一个接口可以吗?当然可以,可是这样后台需要额外维护这种“业务逻辑”。

操作类型( Operation type )

  • 查询( query )
  • 更新( mutation )
  • 订阅( subscription )

GraphQL 实战

我一直提倡,刚开始学习一门新的技术,别看太多文档,先用它的 api, 快速做一个 demo ,跑起来之后。再开始系统学习,这样效率是最高的。所以,下面我们会实战来做一个 GraphQL .

跟着官方文档[1]简单快速创建一个栗子。

初始化项目

mkdir graphql-server-example
cd graphql-server-example
npm init --yes
npm install apollo-server graphql
touch index.js

定义 GraphQL schema

打开 index.js ,将以下代码复制粘贴进去。

const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
  type Book {
    title: String
    author: String
  }
  type Query {
    books: [Book]
  }
`;

定义 resolver

我们在上面的 index.js 文件后面,继续复制粘贴

const resolvers = {
  Query: {
    books: () => books,
  },
};
// 一般会在 resolvers 中连接数据库获取数据,但是为了简单,我们直接返回定义好的数据
const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
];

创建一个 ApolloServer 实例

我们在上面的 index.js 文件后面,继续复制粘贴

const server = new ApolloServer({ typeDefs, resolvers });

// 启动服务
server.listen().then(() => {});

在命令行中运行 index.js

node index.js

好了, 就是这么简单,一个 graphql 就写好了。实际中的项目可能使用 egg 或者 koa 或者 express。本质的思想是一样的, 都是先定义 GraphQL schema ,再定义 resolver ,resolver 这里从不同地方取数,再之后就是传递 schema 和 resolver,创建实例。

工具 GraphiQL

上面的代码运行起来了,要去哪里调用?如果是用 RESTful api ,我们会用 postman 来测试接口是否可以跑通。同样的,GraphQL 可以用 GraphiQL 来测试。

  • 按需取用: 一文快速入门 Graphql
  • 除了可以用来测试接口之后,它还是一个文档工具,使用 GraphiQL 可以很容易地让人感受到“代码即文档”的快乐。
一文快速入门 Graphql
  • 你可以在这里查看测试的历史
一文快速入门 Graphql

Schemas

从上面的例子看出,要先定义 Schemas, 那我们就来学习下 Schemas。如果你使用过 Typescript ,会发现它的类型跟 Typescript 特别的类似。

基本类型

Int, Float, String, Boolean, ID(唯一标识符)

其他类型

  • enum, Date, interface
  • !(不可为 null) 可通过 Union types 或 implements 扩展上面的类型。

2 个特殊类型

查询(query)和变更类型(mutation)

自定义类型

查看官方文档[2]

Resolver

我们可以简单地理解成,针对我们暴露的接口,调用相应的方法去取数返回。resolver 的解析规则是, 从外到内依次处理查询块,为每一个查询块执行对应的 resolver 函数,并传递外层调用返回的结果作为第一个参数,也就是下面代码中的 obj 。

resolver 函数它接收 4 个参数

fieldName(obj, args, context, info) {
  result
}
// obj:解析程序在父字段上返回的结果的对象
// args:查询中传入的参数
// context:这是特定查询中所有解析程序共享的对象,用于包含每个请求的状态,包括身份验证信息,数据加载器实例以及解析该查询时应考虑的任何其他内容
// info:此参数仅在高级情况下使用,但它包含有关查询执行状态的信息,包括字段名称,从根到字段的路径等。 它仅记录在GraphQL.js源代码中。

同样的,我们直接来看一个例子:在 index.js 中修改对应的 Schemas 和 Resolver

const books = [
  {
    title: "Harry Potter and the Chamber of Secrets",
    author: "J.K. Rowling",
    id: 1,
  },
  {
    title: "Jurassic Park",
    author: "Michael Crichton",
    id: 2,
  },
];
const typeDefs = gql`
  type Book {
    title: String
    author: String
    id: ID
  }

  type Query {
    books: [Book]
    book(id: ID!): Book
  }
`;
const resolvers = {
  Query: {
    books: () => books,
    book: (_, { id }) => books.find((book) => book.id == id),
  },
};

由于这个 resolver 函数第一个参数是传递外层调用的返回结果,这里我们没有嵌套 resolver ,所以我们直接用第二个参数 id 获取前端传入的参数。

更多内容查看 resolver 文档[3]

然后我们在 http://127.0.0.1:4000/graphql 或者在客户端 GraphiQL 中测试

一文快速入门 Graphql

已经成功找到对应 id 的数据了,但是这里的 id 是写死的,我们说 graphql 最大的好处是声明式获取,那如何把 id 变成一个变量,让外部传入?一文快速入门 Graphql

query getBook($id: ID!) {
  book(id: $id) {
    title
    id
    author
  }
}

给返回的值设置别名

query getBook($id: ID!) {
  testname: book(id: $id) {
    nickname: title
    id
    author
  }
}

好了,接口正常返回。

更多查询变更可看官方文档[4]

进阶

在实际项目中,我们会将数据库 ,dataloaders 注入到 context 中,方便所有 resolver 调用。

Dataloader

Dataloader 是 facebook 搞的一个 js 库,可以大幅降低数据库的访问频次,从而降低系统负载,经常在 Graphql 场景中使用。通过使用 dataloader,数据库的访问频次可以指数级别下降。

dataloader 是如何工作的呢,可以看下图:

对于 User 表的多次访问,通过 dataloader 去取,会自动合并为一个请求。dataloader 之所以可以实现这样的能力,是因为他把每一次数据请求,都推迟到 node 的 Next Tick 后集中批处理运行,这样就可以对请求进行加工合并。

关注「前端加加」,回复 graphql demo 即可获取完整代码

参考资料

[1]

官方文档: https://www.apollographql.com/docs/apollo-server/getting-started/

[2]

官方文档: https://graphql.org/learn/schema/

[3]

resolver 文档: https://www.graphql-tools.com/docs/resolvers

[4]

官方文档: https://graphql.cn/learn/queries/?spm=ata.13261165.0.0.5e996a21sHCP3T