使用 GraphQL 进行声明式数据获取

Avatar of Nilan Marktanner
Nilan Marktanner

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

以下是来自 Graph.cool 的 Nilan Marktanner 的客座文章。 我不知道你们怎么样,但我职业生涯中花了很多时间处理 REST API。 一直都在尝试弄清楚要访问哪个 URL、预期返回什么数据以及如何控制这些数据。 乍一看,GraphQL 似乎简化了 API 创建者和使用者双方的操作。 让我们听听 Nilan 的解释吧。

在设计各种 Web 服务的 API 架构的标准方面,REST 已经成为多年来的行业标准。 随着 REST 的广泛普及,GraphQL 替代它成为前端和后端之间 API 桥梁的说法遭到了很多质疑。 但 GraphQL 已经发展得非常成熟,正如 Facebook 宣布 GraphQL 投入生产 和 GitHub 揭示其 GitHub GraphQL API 所证明的那样。

GraphQL 所需的基础设施

与 REST 一样,在使用 GraphQL API 之前,我们需要几个组件。

  • GraphQL 客户端 - 这使我们能够获取或修改存储在后端的数据。 如果您处理的是较小的项目,简单的 HTTP 请求可以很好地完成任务,因为请求和响应都以 JSON 编码。
  • GraphQL 后端 - 我们必须公开一个 GraphQL 架构,该架构将我们的 API 描述为类型系统。 类型系统由称为查询(用于获取数据)和变异(用于修改数据)的不同方法组成。 这里才是真正的工作所在,因为我们必须在这些方法和数据层之间实现映射。 例如,查询 allUsers 用于获取所有用户,可能映射到类似 SELECT * FROM USERS 的 SQL 查询。

在本文中,我们将探讨 Instagram 克隆的可能 GraphQL API,并重点介绍 GraphQL 带来的某些优势。

GraphQL 架构

REST API 的端点通常让人联想起数据库的实际架构。 类似 /users/posts 的端点,它们允许访问数据库表 UserPost,非常普遍。

然而,GraphQL 架构需要通过两步过程构建为类型系统。

步骤 1

对象类型由诸如 String 和 Boolean 等基本类型组成。 在我们的例子中,我们将使用 IDL 语法构建 UserPost 对象类型。

type User {
  id: String!
  name: String
  posts: [Post]
}

type Post {
  id: String!
  imageUrl: String
  description: String
  author: User
}

我们有一个 User 对象类型,它包含类型为 String 的 name 字段和类型为 Post 列表(用 [Post] 表示)的 posts 字段,以及一个 Post 对象类型,它包含类型为 String 的 imageUrldescription 字段以及类型为 Userauthor 字段。

除了上面提到的字段之外,两个对象类型都具有 id 字段,它是一个必需的 String(用 String! 表示)。

步骤 2

然后使用这些类型来定义实际的查询,例如 allUsers 查询,它可以用于获取数据库中所有现有用户。 您基本上可以公开任何想到的内容,但最受欢迎的是用于获取特定类型的所有项目或一个项目的查询,以及用于创建、更新和删除特定类型项目的变异。

工具

编码在 GraphQL 架构中的类型系统为 GraphiQL 等强大工具铺平了道路,例如 GraphiQL 由 Facebook 维护。 它允许您以一种轻松的方式探索 GraphQL API。

自动完成功能(如 GIF 中所示)和自动生成的文档大大提高了开发人员体验,通常足以开始使用 GraphQL API。 现在,让我们仔细看看一些查询和变异!

GraphQL 查询

GraphQL 中的查询是声明性的和分层的。 我们将在片刻后看到这到底意味着什么,但前端应用程序中快速变化的数据需求使这成为 GraphQL 最大的卖点之一。

查询基础

假设我们只对所有用户的 idname 感兴趣。 声明性意味着我们准确地声明了我们感兴趣的内容。

query {
  allUsers {
    id
    name
  }
}

查询响应仅包含我们刚刚声明的字段。

{
  "data": {
    "allUsers": [
      {
        "id": "some-id",
        "name": "Nilan"
      },
      {
        "id": "another-id",
        "name": "Chris"
      }
    ]
  }
}

我们可以看到查询响应的结构与查询结构非常接近。 如果我们想通过查询获取更多数据,只需包含更多字段即可。

query {
  allUsers {
    id
    name
    posts {
      imageUrl
    }
  }
}

这里我们看到了分层意味着什么。 该查询遵循架构的关系层次结构,我们可以为所有帖子选择我们感兴趣的字段。 响应可能是。

{
  "data": {
    "allUsers": [
      {
        "id": "some-id",
        "name": "Nilan",
        "posts": [
          {
            "imageUrl": "https://unsplash.it/200/300?image=31"
          },
          {
            "imageUrl": "https://unsplash.it/200/300?image=38"
          }
        ]
      },
      {
        "id": "another-id",
        "name": "Chris",
        "posts": [
          {
            "imageUrl": "https://unsplash.it/200/300?image=99"
          }
        ]
      }
    ]
  }
}

同样重要的是要注意,我们只是更改了查询,而没有触碰 GraphQL 后端,它仍然有效! 一旦我们想出了一个可靠的类型系统,并在 GraphQL 架构中公开它,我们就可以在前端动态更改查询,而 GraphQL 服务器会立即给出正确的响应。 这也意味着不再需要像 REST 中那样费心处理多个端点或 API 版本。

像我们之前那样组合分层字段也会减少前端和后端之间的 HTTP 请求数量。 使用 REST 获取用户及其所有朋友通常至少需要两个请求,而我们只需要一个请求。

高级查询功能

GraphQL 查询接受通常用于提供高级功能(例如过滤或分页)的查询参数。 我们这里不会深入探讨,但让我们看几个快速示例。

过滤非常灵活且强大。 例如,我们只能显示名为 Chris 的用户。

query {
  allUsers(filter: {name: "Chris"}) {
    id
    name
  }
}

不出所料,查询响应仅包含一个用户。

{
  "data": {
    "allUsers": [
      {
        "id": "another-id",
        "name": "Chris"
      }
    ]
  }
}

使用分页,我们可以表示我们感兴趣的连续数据项的数量。 如果我们想构建一个类似于 Google 搜索结果的包含多个页面的提要,这尤其有用。

让我们只查询第一个用户。 我们可以使用 first 参数来完成此操作。

query {
  allUsers(first: 1) {
    id
    name
  }
}

正如预期的那样,我们只得到了第一个用户。

{
  "data": {
    "allUsers": [
      {
        "id": "some-id",
        "name": "Nilan"
      }
    ]
  }
}

现在,如果我们想查询第二个用户,我们可以跳过第一个用户,并通过将 firstskip 相结合来仅获取下一个用户。

query {
  allUsers(first: 1, skip: 1) {
    id
    name
  }
}

这将返回第二个用户。

{
  "data": {
    "allUsers": [
      {
        "id": "another-id",
        "name": "Chris"
      }
    ]
  }
}

这些只是我们可以使用查询参数的一些方法。 如果我们想提供另一个类似的功能,例如按字段对查询响应进行排序,我们必须在 GraphQL 架构中定义一个新的查询参数,并在后端相应地实现该功能。

变异

变异是查询的对应物。 虽然我们可以使用查询获取数据,但变异允许我们创建新数据或更新或删除现有数据项。 相应的变异可以分别称为 createPostupdatePostdeletePost

新数据项的字段值通过查询参数提供。 变异也需要选择将作为查询响应返回的字段。 用于创建新帖子的变异可能如下所示。

mutation {
  createPost(imageUrl: "https://unsplash.it/200/300?image=27", description: "#random", authorId: "some-id") {
    id
  }
}

响应将包含新创建的 id

{
  "data": {
    "createPost": {
      "id": "some-newly-generated-id"
    }
  }
}

结论

本文概述了 GraphQL 作为 REST 的替代方案。

我们了解到,GraphQL 客户端需要连接前端和后端,以及 Instagram 克隆的 GraphQL 架构示例。我们学习了如何使用查询和变异来获取和修改数据,以及 GraphQL API 如何让我们以声明式和分层的方式精确地选择所需的字段。我们还简要了解了过滤和分页这两个强大的概念,同时 GraphiQL 展示了依赖于定义类型系统的工具的强大功能。

如果您想了解更多关于 GraphQL 的信息,您可以查看 GraphQL 网站的学习部分学习 GraphQL,这两个都是非常容易理解的资源。

RelayApollo Client 是两个流行的 JavaScript GraphQL 客户端。如果您对 Relay 感兴趣,请访问 学习 Relay,获得一个交互式和全面的 Relay 入门指南。

graphql-jsSangria 是两个流行的库,分别用于在 JavaScript 和 Scala 中构建 GraphQL 后端。

要快速开始使用 GraphQL,您可以查看 Graphcool,它允许您以图形方式定义模型和字段,自动生成 GraphQL 后端,并提供其他功能,如高级权限系统和开箱即用的文件管理。