GraphQL与REST-GraphQL教程

本文概述

  • Graphql与REST:为什么要删除REST?
  • 如何从GraphQL开始
  • 下一步是什么?
你可能听说过这个街区的新孩子:GraphQL。如果不是这样, 那么简而言之, GraphQL是一种获取API的新方法, 是REST的替代方法。它最初是Facebook的一个内部项目, 自从开源以来, 它已经吸引了很多人。
本文的目的是帮助你轻松地从REST过渡到GraphQL, 无论你是否已经打算使用GraphQL还是愿意尝试一下。无需具备GraphQL的先验知识, 但需要对REST API有所了解才能理解本文。
GraphQL与REST-GraphQL教程

文章图片
本文的第一部分将首先给出三个我为何认为GraphQL优于REST的原因。第二部分是有关如何在后端上添加GraphQL端点的教程。
Graphql与REST:为什么要删除REST? 如果你仍然对GraphQL是否适合你的需求犹豫不决, 请在此处给出” REST vs. GraphQL” 的相当客观的概述。但是, 由于我使用GraphQL的三大原因, 请继续阅读。
原因1:网络性能
假设你在后端有一个用户资源, 其中包含名字, 姓氏, 电子邮件和其他10个字段。在客户端上, 你通常只需要几个。
在/ users端点上进行REST调用将返回用户的所有字段, 而客户端仅使用所需的字段。显然存在一些数据传输浪费, 这可能是移动客户端要考虑的问题。
GraphQL默认情况下会获取最小的数据。如果只需要用户的名字和姓氏, 请在查询中指定。
下面的接口称为GraphiQL, 它类似于GraphQL的API资源管理器。出于本文的目的, 我创建了一个小项目。该代码托管在GitHub上, 我们将在第二部分中深入研究它。
在界面的左窗格中是查询。在这里, 我们正在获取所有用户-我们将使用REST执行GET / users-并且仅获取其名字和姓氏。
询问
query { users { firstname lastname } }

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

如果我们也想获取电子邮件, 则在” 姓氏” 下方添加” 电子邮件” 行即可解决问题。
某些REST后端确实提供了/ users?fields = firstname, lastname之类的选项以返回部分资源。物有所值, Google推荐。但是, 默认情况下未实现它, 它使请求几乎不可读, 尤其是当你使用其他查询参数时:
  • &status = active过滤活动用户
  • &sort = createdAat按用户创建日期对用户进行排序
  • &sortDirection = desc, 因为你显然需要它
  • &include = projects包含用户的项目
这些查询参数是添加到REST API的补丁程序, 用于模仿查询语言。 GraphQL首先是一种查询语言, 它使请求从一开始就简洁明了。
原因2:” 包含与端点” 设计选择
假设我们要构建一个简单的项目管理工具。我们有三个资源:用户, 项目和任务。我们还定义了资源之间的以下关系:
GraphQL与REST-GraphQL教程

文章图片
以下是我们向世界展示的一些端点:
终点 描述
GET /用户 列出所有用户
GET /用户/:ID 获取ID为:id的单个用户
GET /用户/:ID /项目 获取一个用户的所有项目
端点很简单, 易于阅读并且组织良好。
当我们的请求变得更加复杂时, 事情变得更加棘手。让我们以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与REST” 的大多数辩论中, 人们认为他们必须选择两者之一。这是不正确的。
现代应用程序通常使用几种不同的服务, 这些服务公开了几种API。实际上, 我们可以将GraphQL视为所有这些服务的网关或包装。所有客户端都将命中GraphQL端点, 而该端点将命中数据库层, 诸如ElasticSearch或Sendgrid之类的外部服务或其他REST端点。
GraphQL与REST-GraphQL教程

文章图片
使用这两种方法的第二种方法是在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中由强类型化的类型表示。你的模型和GraphQL类型之间应该存在一对一的映射。我们的用户类型为:
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/userscreateProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): ProjectcreateTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }

请注意, 我们在这里介绍了新类型, 分别称为UserInput, ProjectInput和TaskInput。 REST也是一种常见做法, 即创建用于创建和更新资源的输入数据模型。在这里, 我们的UserInput类型是没有id和projects字段的User类型, 请注意关键字input而不是type:
input UserInput { firstname: String lastname: String email: String }

架构图
通过类型, 查询和变异, 我们定义了GraphQL模式, 这是GraphQL终结点向世人展示的内容:
schema { query: RootQuery mutation: RootMutation }

该模式是强类型的, 正是这种模式使我们能够在GraphiQL中拥有那些方便的自动完成功能。
解析器
现在我们有了公共模式, 是时候告诉GraphQL当请求这些查询/变异中的每一个时该怎么做了。解析者会努力工作;他们可以, 例如:
  • 击中内部REST端点
  • 呼叫微服务
  • 打数据库层做CRUD操作
【GraphQL与REST-GraphQL教程】我们在示例应用程序中选择第三个选项。让我们看一下我们的解析器文件:
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中以” 用户” 类型请求项目字段时, 此联接将附加到数据库查询中。
最后, 是解析器:
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服务器 由Apollo团队创建的多合一GraphQL服务器。
其他平台的实现 Ruby, PHP等
客户端工具 描述
中继 用于将React与GraphQL连接的框架。
阿波罗客户。 一个带有React, Angular 2和其他前端框架绑定的GraphQL客户端。
总之, 我相信GraphQL不仅仅是炒作。它不会在明天取代REST, 但是它确实为真正的问题提供了有效的解决方案。它是相对较新的, 并且最佳实践仍在发展中, 但这绝对是我们将在未来几年听到的技术。

    推荐阅读