GraphQL 与 REST - GraphQL 教程

已发表: 2022-03-11

你可能听说过这个街区的新孩子:GraphQL。 如果不是,GraphQL 是一种获取 API 的新方法,是 REST 的替代方案。 它最初是 Facebook 的一个内部项目,由于它是开源的,因此获得了很大的关注。

本文的目的是帮助您轻松地从 REST 过渡到 GraphQL,无论您是否已经决定使用 GraphQL,或者您只是愿意尝试一下。 不需要 GraphQL 的先验知识,但需要熟悉 REST API 才能理解本文。

GraphQL 与 REST - GraphQL 教程

文章的第一部分将首先给出我个人认为 GraphQL 优于 REST 的三个原因。 第二部分是关于如何在后端添加 GraphQL 端点的教程。

Graphql 与 REST:为什么要放弃 REST?

如果您仍然对 GraphQL 是否适合您的需求犹豫不决,这里给出了“REST 与 GraphQL”的相当广泛和客观的概述。 但是,对于我使用 GraphQL 的三大理由,请继续阅读。

原因一:网络性能

假设您在后端有一个用户资源,其中包含名字、姓氏、电子邮件和其他 10 个字段。 在客户端上,您通常只需要其中的几个。

/users端点上进行 REST 调用会返回用户的所有字段,客户端只使用它需要的字段。 显然存在一些数据传输浪费,这可能是移动客户端的考虑因素。

默认情况下,GraphQL 会获取尽可能少的数据。 如果您只需要用户的名字和姓氏,请在查询中指定。

下面的接口叫做 GraphiQL,它就像一个 GraphQL 的 API 浏览器。 为了本文的目的,我创建了一个小项目。 代码托管在 GitHub 上,我们将在第二部分深入研究。

在界面的左侧窗格中是查询。 在这里,我们正在获取所有用户——我们将使用 REST GET /users users——并且只获取他们的名字和姓氏。

询问

query { users { firstname lastname } }

结果

{ "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }

如果我们也想获取电子邮件,在“姓氏”下方添加“电子邮件”行就可以了。

一些 REST 后端确实提供像/users?fields=firstname,lastname这样的选项来返回部分资源。 对于它的价值,谷歌推荐它。 但是,默认情况下它没有实现,并且它使请求几乎不可读,尤其是当您折腾其他查询参数时:

  • &status=active过滤活跃用户
  • &sort=createdAat按创建日期对用户进行排序
  • &sortDirection=desc因为你显然需要它
  • &include=projects包含用户的项目

这些查询参数是添加到 REST API 以模仿查询语言的补丁。 GraphQL 首先是一种查询语言,它使请求从一开始就简洁准确。

原因 2:“包含与端点”设计选择

假设我们要构建一个简单的项目管理工具。 我们拥有三种资源:用户、项目和任务。 我们还定义了资源之间的以下关系:

资源之间的关系

以下是我们向世界公开的一些端点:

端点描述
GET /users 列出所有用户
GET /users/:id 获取具有 id 的单个用户:id
GET /users/:id/projects 获取一个用户的所有项目

端点简单、易读且组织良好。

当我们的请求变得更加复杂时,事情就会变得更加棘手。 让我们以GET /users/:id/projects端点为例:假设我只想在主页上显示项目的标题,但在仪表板上显示项目+任务,而不进行多次 REST 调用。 我会打电话给:

  • GET /users/:id/projects
  • 在仪表板页面上GET /users/:id/projects?include=tasks (例如),以便后端附加所有相关任务。

通常的做法是添加查询参数?include=...以使其工作,甚至被 JSON API 规范推荐。 像?include=tasks这样的查询参数仍然是可读的,但不久之后,我们将得到?include=tasks,tasks.owner,tasks.comments,tasks.comments.author

在这种情况下,创建/projects端点来执行此操作是否更明智? /projects?userId=:id&include=tasks之类的东西,因为我们会少包含一层关系? 或者,实际上, /tasks?userId=:id端点也可以工作。 这可能是一个困难的设计选择,如果我们有一个多对多的关系,那就更复杂了。

GraphQL 在任何地方都使用include方法。 这使得获取关系的语法强大且一致。

这是一个从 id 为 1 的用户那里获取所有项目和任务的示例。

询问

{ user(id: 1) { projects { name tasks { description } } } }

结果

{ "data": { "user": { "projects": [ { "name": "Migrate from REST to GraphQL", "tasks": [ { "description": "Read tutorial" }, { "description": "Start coding" } ] }, { "name": "Create a blog", "tasks": [ { "description": "Write draft of article" }, { "description": "Set up blog platform" } ] } ] } } }

如您所见,查询语法很容易阅读。 如果我们想更深入地包括任务、评论、图片和作者,我们不会三思而后行如何组织我们的 API。 GraphQL 使获取复杂对象变得容易。

原因3:管理不同类型的客户

在构建后端时,我们总是首先尝试使 API 尽可能地被所有客户端广泛使用。 然而,客户总是希望少打电话,多取钱。 通过深度包含、部分资源和过滤,Web 和移动客户端发出的请求可能彼此之间存在很大差异。

对于 REST,有几个解决方案。 我们可以创建自定义端点(即别名端点,例如/mobile_user )、自定义表示( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json ),甚至是客户端- 特定的 API(就像 Netflix 曾经做过的那样)。 这三个都需要后端开发团队付出额外的努力。

GraphQL 为客户端提供了更多的权力。 如果客户端需要复杂的请求,它会自己构建相应的查询。 每个客户端可以以不同的方式使用相同的 API。

如何开始使用 GraphQL

在当今大多数关于“GraphQL vs. REST”的辩论中,人们认为他们必须选择两者之一。 这是不正确的。

现代应用程序通常使用几种不同的服务,这些服务公开了几个 API。 我们实际上可以将 GraphQL 视为所有这些服务的网关或包装器。 所有客户端都将访问 GraphQL 端点,而该端点将访问数据库层、外部服务(如 ElasticSearch 或 Sendgrid)或其他 REST 端点。

GraphQL 与 REST 端点的比较

使用两者的第二种方法是在您的 REST API 上有一个单独的/graphql端点。 如果您已经有大量客户端访问您的 REST API,但您想在不损害现有基础设施的情况下尝试 GraphQL,这将特别有用。 这就是我们今天正在探索的解决方案。

如前所述,我将通过 GitHub 上提供的一个小示例项目来说明本教程。 它是一个简化的项目管理工具,包含用户、项目和任务。

该项目使用的技术是用于 Web 服务器的 Node.js 和 Express,作为关系数据库的 SQLite,以及作为 ORM 的 Sequelize。 三个模型(用户、项目和任务)在models文件夹中定义。 REST 端点/api/users/api/projects/api/tasks向世界公开,并在rest文件夹中定义。

请注意,GraphQL 可以使用任何编程语言安装在任何类型的后端和数据库上。 选择这里使用的技术是为了简单和可读性。

我们的目标是在不删除 REST 端点的情况下创建/graphql端点。 GraphQL 端点将直接访问数据库 ORM 以获取数据,因此它完全独立于 REST 逻辑。

类型

数据模型在 GraphQL 中由types表示,它们是强类型的。 您的模型和 GraphQL 类型之间应该存在一对一的映射。 我们的User类型将是:

 type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }

查询

查询定义了可以在 GraphQL API 上运行的查询。 按照惯例,应该有一个RootQuery ,其中包含所有现有的查询。 我还指出了每个查询的 REST 等价物:

 type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }

突变

如果查询是GET请求,突变可以被视为POST / PATCH / PUT / DELETE请求(尽管它们实际上是查询的同步版本)。

按照惯例,我们将所有的突变放在一个RootMutation中:

 type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }

请注意,我们在这里引入了新类型,称为UserInputProjectInputTaskInput 。 这也是 REST 的常见做法,用于创建用于创建和更新资源的输入数据模型。 在这里,我们的UserInput类型是没有idprojects字段的User类型,请注意关键字input而不是type

 input UserInput { firstname: String lastname: String email: String }

架构

通过类型、查询和突变,我们定义了GraphQL 模式,这是 GraphQL 端点向世界公开的内容:

 schema { query: RootQuery mutation: RootMutation }

这个模式是强类型的,这让我们能够在 GraphiQL 中拥有那些方便的自动完成功能。

解析器

现在我们有了公共模式,是时候告诉 GraphQL 在请求每个查询/突变时要做什么了。 解决者做艰苦的工作; 例如,他们可以:

  • 命中内部 REST 端点
  • 调用微服务
  • 命中数据库层进行 CRUD 操作

我们在示例应用程序中选择第三个选项。 让我们看看我们的解析器文件:

 const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }

这意味着,如果在 GraphQL 上请求user(id: ID!)查询,那么我们从数据库返回User.findById() ,这是一个 Sequelize ORM 函数。

在请求中加入其他模型怎么样? 好吧,我们需要定义更多的解析器:

 User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */

因此,当我们在 GraphQL 中请求User类型中的projects字段时,此连接将附加到数据库查询中。

最后,突变解析器:

 RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }

你可以在这里玩这个。 为了保持服务器上的数据干净,我禁用了突变解析器,这意味着突变不会在数据库中执行任何创建、更新或删除操作(因此在界面上返回null )。

询问

query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }

结果

{ "data": { "user": { "firstname": "Alicia", "lastname": "Smith", "projects": [ { "name": "Email Marketing Campaign", "tasks": [ { "description": "Get list of users" }, { "description": "Write email template" } ] }, { "name": "Hire new developer", "tasks": [ { "description": "Find candidates" }, { "description": "Prepare interview" } ] } ] } } }

为现有应用重写所有类型、查询和解析器可能需要一些时间。 但是,有很多工具可以帮助您。 例如,有一些工具可以将 SQL 模式转换为 GraphQL 模式,包括解析器!

把所有东西放在一起

使用定义明确的模式和解析器对模式的每个查询执行什么操作,我们可以在后端挂载一个/graphql端点:

 // Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));

我们可以在后端拥有一个漂亮的 GraphiQL 界面。 要在没有 GraphiQL 的情况下发出请求,只需复制请求的 URL,然后使用 cURL、AJAX 或直接在浏览器中运行它。 当然,有一些 GraphQL 客户端可以帮助您构建这些查询。 请参阅下面的一些示例。

下一步是什么?

本文的目的是让您了解 GraphQL 的样子,并向您展示在不丢弃 REST 基础架构的情况下尝试 GraphQL 绝对是可能的。 了解 GraphQL 是否适合您的需求的最佳方法是亲自尝试。 我希望这篇文章能让你潜水。

有很多我们没有在本文中讨论的特性,例如实时更新、服务器端批处理、身份验证、授权、客户端缓存、文件上传等。了解这些特性的绝佳资源是如何使用 GraphQL。

以下是一些其他有用的资源:

服务器端工具描述
graphql-js GraphQL 的参考实现。 您可以将它与express-graphql一起使用来创建服务器。
graphql-server 由 Apollo 团队创建的一体化 GraphQL 服务器。
其他平台的实现红宝石、PHP 等
客户端工具描述
中继用于连接 React 和 GraphQL 的框架。
阿波罗客户端。 一个 GraphQL 客户端,绑定了 React、Angular 2 和其他前端框架。

总之,我相信 GraphQL 不仅仅是炒作。 它明天还不会取代 REST,但它确实为真正的问题提供了一个高性能的解决方案。 它相对较新,最佳实践仍在发展中,但它绝对是我们将在未来几年内听到的一项技术。