GraphQL从入门到实践

一、Why GraphQL?

现在有一个需求:根据文章ID来查找对应的 User 信息。

数据结构如下:

Users:
{
  "id": 1,
  "fname": "Richie",
  "age": 27,
  "likes": 8
},
{ 
  "id": 2,
  "fname": "Betty",
  "age": 20,
  "likes": 205
},
{ 
  "id" : 3,
  "fname": "Joe",
  "age": 28,
  "likes": 10
}
Posts:
{
  id: 1,
  userId: 2,
  body: "Hello how are you?"
},
{
  id: 1,
  userId: 3,
  body: "What's up?"
},
{
  id: 1,
  userId: 1,
  body: "Let's learn GraphQL"
}

1、传统方式的局限性

常见的方案是:定义两个接口,分别根据user id和post id 获取对应的 userpost 信息。

> https://localhost:4000/users/:id
> https://localhost:4000/posts/:id

整个过程如下:

GET https://localhost:4000/posts/1
Response:
{
  id: 1,
  userId: 2,
  body: "Hello how are you?"
}
我们现在拿到文章id为2的userId, 然后调用另一个接口获取用户信息
GET https://localhost:4000/users/2
Response:
{ 
  "id": 2,
  "fname": "Betty",
  "age": 20,
  "likes": 205
}

整个过程查询了两次数据库。

还没完,假如前端页面只用到了fname信息呢?

有人说从接口数据里只取 fname 就好啦。 但是你想过没有,接口返回给我们的是用户的所有字段: id, age, likes等。这些字段会随着请求量的增大,会消耗网络带宽,给我们的服务器带来压力。

又有人会说,那我重新定义一个接口专用于返回用户的 fname, 如:

https://localhost:4000/userByfname/:id

这样确实能避免我们上面提到的问题,但是会带来一些新的问题: 第一,我们需要根据user的字段创建分别创建接口。第二,随着应用的变化,user的字段信息肯定会发生变化,我们需要同时维护好这些接口,给开发人员带来心智负担。

那么我们如果使用 GraphQL 会怎么样

2、GraphQL 的优势

GraphQL 中只需创建一个 query 语句:

{
  posts(id: 1) {
    body
    user {
      fname
    }
  }
}

如果你仅仅只需用到 age 字段,你可以修改 query 语句:

{
  posts(id: 1) {
    body
    user {
      age
    }
  }
}

只需保证 user 字段在 GraphQLschema 中都提前定义好就行。

GraphQL 最大的优势就是既不需要新建接口,也无需与后端服务多次通信,就能精确的获取到我们想要的字段信息

二、GraphQL 基础入门

先从最基本的 GraphQL 操作类型开始:

1、Query

顾名思义,Query用于查询数据。是以字符串的形式通过Post请求中body体中传给服务器。也就是所有的GraphQL类型都是post请求。

以下Query用于从数据库中取出所有 users 的 fname 和 age信息:

query {
  users {
    fname
    age
  }
}

理想情况下,服务器应返回的数据格式是这样的:

data : {
  users [
    {
      "fname": "Joe",
      "age": 23
    },
    {
      "fname": "Betty",
      "age": 29
    }
  ]
}

如果成功,会返回一个含有key 为 data的JSON对象;如果失败,返回的key就是error; 我们可以根据这个来处理错误。

2、Mutation

Mutation 用于向数据库写入数据。可以把它想象为 rest 中的 post 和 put请求:

mutation createUser{
  addUser(fname: "Richie", age: 22) {
    id
  }
}

定义了一个名为 createUser 的 mutation, 根据传入的fname和age向数据库添加一个user; 同时定义了id作为返回值:

data : {
  addUser : "a36e4h"
}

3、Subscription

subscription 赋予客户端实时监听数据变化的能力,利用的 websockets 来实现的。

subscription listenLikes {
  listenLikes {
    fname
    likes
  }
}

上面定义了一个监听 usersfnamelikes 两个字段,一旦其中任何一个字段发生变化,都会以下面的形式通知客户端:

data: {
  listenLikes: {
    "fname": "Richie",
    "likes": 245
  }
}

假如有一个页面需要实时显示用户 likes 字段的变化,这个特性就变得非常有用。

以上就是 GraphQL 中最基本的操作类型, 虽然介绍的很简单,但是已经足够了。下面我就利用上面介绍的知识设计并且实现一个GraphQL 案例,来直观感受 GraphQL 的强大。

三、GraphQL 实战

首先创建一个 GraphQL 服务来分别响应 query, mutationsubscriptions 这三种操作。

1、初始化工程

> mkdir graphql-server-demo
> cd graphql-server-demo
> npm init -y

安装依赖:

> npm install apollo-server graphql nodemon --save

引入 apollo-server 是因为它是一个开源的且提供强大能力的 GraphQL 服务器。

在package.json中的scripts加入以下脚本:

"scripts": { "start": "nodemon -e js, json, graphql" }

2、定义数据

在这个例子中,我们将数据定义为一个JSON对象。

在真实项目中通常是使用数据库来存放数据。

我们把数据定义在index.js中:

const users = [
  {
    id: 1,
    fname: 'Richie',
    age: 27,
    likes: 0,
  },
  {
    id: 2,
    fname: 'Betty',
    age: 20,
    likes: 205,
  },
  {
    id: 3,
    fname: 'Joe',
    age: 28,
    likes: 10,
  },
];
const posts = [
  {
    id: 1,
    userId: 2,
    body: "Hello how are you?"
  },
  {
    id: 1,
    userId: 3,
    body: "What's up?"
  },
  {
    id: 1,
    userId: 1,
    body: "Let's learn GraphQL"
  },
]

现在准备工作已经就绪,让我们开始实现这个例子吧。

3、Schema

第一步是要设计我们的 Schema,它包含了两个相互依赖的对象:TypeDefsResolvers

3.1 TypeDefs

之前我们学会了GraphQL的操作类型。为了让服务器知道什么类型是可以识别处理的, 而TypeDefs的作用就是定义这些类型:

const typeDefs = gql`
  type User {
    id: Int
    fname: String
    age: Int
    likes: Int
    posts: [Post]
  }
  type Post {
    id: Int
    user: User
    body: String
  }
  type Query {
    users(id: Int!): User!
    posts(id: Int!): Post!
  }
  type Mutation {
    incrementLike(fname: String!) : [User!]
  }
  type Subscription {
    listenLikes : [User]
  }
`;

首先定义了一个User,包含了 id, fname, age, likes, posts 五个属性,同时规定了这些属性的类型要么是String要么是Int

在GraphQL中支持四种基本类型:String, Int, Float, Boolean
代表这个字段是非空。

另外我们还定义了Query, MutationSubscription 等操作.

  • 定义两个query, 其中users 接收一个id参数, 返回类型是User。 另一个query posts 也接收一个id参数,且返回类型是Post
type Query {
  users(id: Int!): User!
  posts(id: Int!): Post!
}
  • Mutation 名叫:incrementLike, 接收一个参数fname, 并且返回一个User数组。
type Mutation {
  incrementLike(fname: String!) : [User!]
}
  • Subscription 名叫: listenLikes, 返回一个User数组。
type Subscription {
  listenLikes : [User]
}

现在,我们只是定义了类型,服务器并不知道如何响应客户端请求,所以需要为我们定义的类型提供一些处理逻辑, 我们把这个叫做 Resolvers

3.2 Resolvers

让我们继续编写resolvers:

const resolvers = {
  Query: {
    users(root, args) { return users.filter(user => user.id === args.id)[0] },
    posts(root, args) { return posts.filter(post => post.id === args.id)[0] }
  },
  User: {
    posts: (user) => {
      return posts.filter(post => post.userId === user.id)
    }
  },
  Post: {
    user: (post) => {
      return users.filter(user => user.id === post.userId)[0]
    }
  },
  Mutation: {
    incrementLike(parent, args) {
      users.map((user) => {
        if(user.fname === args.fname) user.likes++
        return user
      })
      pubsub.publish('LIKES', {listenLikes: users});
      return users
    }
  },
  Subscription: {
    listenLikes: {
      subscribe: () => pubsub.asyncIterator(['LIKES'])
    }
  }
};

正如你所看到的,我们创建了6个resolvers函数:

1、users query 根据传入的id返回相对应的 User 对象。
2、posts query 根据传入的id返回对对应的 Post 对象。
3、因为User TypeDefs中包含一个 posts 字段,所以需指定一个resolver来根据user id匹配到该用户发布过的文章。
4、同上,因为Post TypeDefs中包含一个 user 字段,所以需要指定一个resolver根据userId匹配到文章的作者。
5、incrementLike 作为一个变更操作,用于更新用户的 likes 字段。并将更新后的数据通过消息订阅发布模式发送给LIKES事件。
6、listenLikes 作为一个订阅对象,用于监听LIKES事件,并对此作出响应。

什么是订阅发布?
它是由GraphQL实现的基于websocket的实时通讯系统,websocket将这一切变得非常简单。

现在我们已经定义好typedefs和resolver,那么我们的服务也就可以正常🚀了!

startup

打开浏览器,并输入http://localhost:4000/ 回车:

playground

Apollo GraphQL 提供了一个可交互的web应用,帮助我们测试query和mutations操作的结果:

testresult

至此,我们完成了一个简单的 GraphQL 服务器,并且能响应 Query , MutationSubscription 这些请求操作。😁是不是感觉很酷呢?

四、总结

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。

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

引用某位大神曾经说过的话:

GraphQL将会取代REST

让我们拭目以待。

https://juejin.im/post/5e37e3906fb9a0301e0dfae5

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论