GraphQL 初学者指南
哇,自 2015 年 Facebook 公开发布 GraphQL 以来已经过去了五年。它不再只是一个新鲜事物——GraphQL 生态系统已经非常成熟,在不同的 API 设计方法之间进行选择时应该考虑到这一点。
如果您是 GraphQL 新手,本文将帮助您了解客户端-服务器通信的工作原理以及 GraphQL 与最常用的RESTful API之间的主要区别。
我将向您展示如何从客户端向服务器发出请求,并检查整个过程中发生的情况。那么,让我们开始吧!
架构和数据类型
想象一下,你是一名宇航员👨🚀。你想买一艘宇宙飞船,这样你就可以和朋友们一起遨游宇宙了。作为一名宇航员,你了解宇宙飞船的属性,因此你可以轻松地为它定义一个类型:
type Spaceship {
model: String!
weight: Float
speed: Int
turboEnabled: Boolean
}
为了定义Spaceship
对象类型,我们使用了一种称为“GraphQL 模式定义语言”或简称为GraphQL SDL 的东西。
所有Spaceship
字段都是内置标量类型。GraphQL 有 5 种内置标量类型:Int、Float、String、Boolean和ID。字段类型不限于标量类型,还可以是其他对象类型或枚举类型。
注意我们在类型名称后面使用了感叹号 - String!
。使用感叹号表示我们期望服务器返回该字段的非空值。如果服务器返回该字段的空值,则会触发执行错误。
现在我们知道了如何使用GraphQL SDL,让我们为商店🛒定义一个对象类型,我们可以在其中购买宇宙飞船:
type Shop {
name: String!
address: String!
spaceships: [Spaceship]
}
每家商店都提供各种各样的太空飞船——因此,我们有一个字段类型[Spaceship]
来表示太空飞船的列表。在进一步讨论之前,我们需要定义如何查询数据。为此,我们应该使用一种特殊的Query
对象类型:
type Query {
spaceships: [Spaceship]
shop(name: String!): Shop
}
我们可以将字段视为Query
REST 中的路由——它们是 API 的入口点。通过检查Query
类型,我们可以找到可以从服务器获取哪些数据。在本例中,我们可以获取宇宙飞船列表,也可以按名称获取商店。
最后,我们的 GraphQL 模式如下所示:
type Spaceship {
model: String!
weight: Float
speed: Int!
turboEnabled: Boolean
}
type Shop {
name: String!
address: String!
spaceships: [Spaceship]
}
type Query {
spaceships: [Spaceship]
shop(name: String!): Shop
}
定义模式不应该只是后端开发人员的任务。前端开发人员也应该参与其中,因为最终他们将使用来自服务器的数据,并将模式用作文档。
查询构造
这是客户端发挥作用的部分。我们已经定义了模式,因此我们可以执行查询来获取一些数据。编写查询很简单——基本上就是选择您需要的字段。假设您需要一个宇宙飞船列表,但只需要它们的型号和速度,其他什么都不用做。您可以编写如下查询:
{
spaceships {
model
speed
}
}
之后,向 GraphQL 服务器执行请求,并将查询作为GET
请求的查询参数或请求正文附加POST
。
fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({query: "{ spaceships { model speed } }"})
})
如果一切顺利,您将收到如下回复:
{
"data": {
"spaceships": [
{
"model": "Mercury Conqueror",
"speed": 2000
},
...
]
}
}
此外,如果您想按名称获取商店以及宇宙飞船列表,则无需使用其他查询执行另一个请求。您可以修改之前的查询并添加其他字段。这样,我们只需一个请求即可获得所需的一切。

REST API 世界中的情况略有不同,如果你想获得:
- 宇宙飞船列表,你可能需要
GET
向/spaceships
航线发出请求 - 按名称查找商店,您必须
GET
向/shop/:shopName
路线发出请求

你可能会注意到,我们必须使用 REST 发出更多请求才能获取所需的所有数据。不仅如此,我们还会获取一些并非必需的数据,这意味着我们进行了过度获取,因为端点返回的数据结构是固定的。使用 GraphQL,你无需担心获取不足或过度,因为你只需要请求所需的数据💰。
解析、验证和执行
我们现在位于服务器端;在 REST 中处理请求非常简单——每个路由(端点)都与一个函数(控制器)关联。当服务器收到请求时,它会执行该函数并将结果返回给客户端。在大多数情况下,在到达控制器之前,我们必须解析、验证和清理从客户端收到的数据。
另一方面,GraphQL 从我们的请求中获取查询,并将其解析为抽象语法树(AST)。解析完成后,它将获取我们的模式并根据该模式验证收到的查询。我们无需担心客户端是否未发送所需数据、提供的是字符串而不是数字,或者查询了不存在的字段。GraphQL 会处理这些问题,并在必要时向客户端发出错误警告。如果一切正常,我们就可以进入执行阶段。
执行阶段
GraphQL 需要知道如何解析给定查询的每个字段。提醒一下,我们的Query
对象类型提供了两种可能的查询:spaceships
和shop(name: String!)
。
type Query {
spaceships: [Spaceship]
shop(name: String!): Shop
}
为了教会 GraphQL 如何解析每个字段,我们必须为每个Query
字段编写一个解析器函数。解析器函数可能会访问数据库或执行任何必要的操作来获取数据并返回。
const resolvers = {
Query: {
spaceships(obj, args, context, info) {
return db.findAllSpaceships()
},
shop(obj, args, context, info) {
return db.findShopByName(args.name)
}
}
}
注意:GraphQL 与语言无关,许多不同的语言都支持它。我们这里使用的是 JavaScript。您可以在此处查看有关解析器参数的更多详细信息。
Spaceship
我们也可以编写对象字段的解析器Shop
。例如,如果设置为 ,我们可以解析speed
字段并返回不同的值:turboEnabled
true
const resolvers = {
Query: {...},
Spaceship: {
speed(obj, args, context, info) {
return obj.turboEnabled
? obj.speed * 2
: obj.speed
}
}
}
默认情况下,如果我们省略解析器,GraphQL 会通过返回同名的属性来解析字段。GraphQL 遍历树并解析每个节点(字段)。解析后的值将生成一个与原始查询对应的键值对映射。此结果将发送给请求它的客户端。

GraphQL 用例
GraphQL 的优点在于您可以将其置于现有 API 之上,因此您不必从头开始做所有事情。
使用 GraphQL 的一个常见用例是客户端需要来自多个来源的数据。使用 GraphQL,您可以聚合数据,并让客户端以标准化的方式从单个点使用它。
另一个用例是当有多个不同的客户端使用不同的数据时。这些客户端很可能需要发出多个请求才能获取所需的数据,并且容易出现过度获取或获取不足的情况。使用 GraphQL,您可以让每个客户端选择要获取的数据。
下一步是什么?
我们只是触及了表面;如果您想进一步探索,我建议您查看以下链接:
附注:如果你正在寻找远程 JS 开发人员,请随时联系我 🙂
文章来源:https://dev.to/davinc/graphql-for-beginners-3f1a