我们每个人都有许多兴趣和爱好。例如,我对 JavaScript、90 年代独立摇滚和嘻哈、冷门爵士乐、匹兹堡市、披萨、咖啡以及约翰·卢里主演的电影感兴趣。我们还有家人、朋友、熟人、同学和同事,他们也有自己的社交关系、兴趣和爱好。其中一些关系和兴趣是重叠的,比如我的朋友 Riley 就和我一样喜欢 90 年代的嘻哈和披萨。另一些则没有,比如我的同事 Harrison,他更喜欢 Python 而不是 JavaScript,只喝茶,并且更喜欢当前的流行音乐。总而言之,我们每个人都与我们生活中的人们以及我们的关系和兴趣重叠的方式形成了一个关联图。
这些类型的互连数据正是 GraphQL 最初在 API 开发中着手解决的挑战。通过编写 GraphQL API,我们能够有效地连接数据,从而降低复杂性和请求数量,同时允许我们精确地向客户端提供其所需的数据。(如果您更喜欢 GraphQL 的比喻,请查看 在鸡尾酒会上遇见 GraphQL。)
在本文中,我们将使用 Apollo Server 包在 Node.js 中构建一个 GraphQL API。为此,我们将探索基本的 GraphQL 主题,编写 GraphQL 模式,开发代码来解析我们的模式函数,并使用 GraphQL Playground 用户界面访问我们的 API。
什么是 GraphQL?
GraphQL 是一种用于 API 的开源查询和数据操作语言。它的开发目标是为数据提供单个端点,允许应用程序精确请求所需的数据。这不仅简化了我们的 UI 代码,而且通过限制需要通过网络发送的数据量来提高性能。
我们正在构建什么
要跟随本教程,您需要 Node v8.x 或更高版本,并熟悉使用命令行。
我们将为书籍摘录构建一个 API 应用程序,允许我们存储我们阅读内容中的难忘段落。API 用户将能够对他们的摘录执行“CRUD”(创建、读取、更新、删除)操作
- 创建新的摘录
- 读取单个摘录以及摘录列表
- 更新摘录的内容
- 删除摘录
入门
首先,为我们的项目创建一个新目录,初始化一个新的节点项目,并安装我们需要的依赖项,以开始操作。
# make the new directory
mkdir highlights-api
# change into the directory
cd highlights-api
# initiate a new node project
npm init -y
# install the project dependencies
npm install apollo-server graphql
# install the development dependencies
npm install nodemon --save-dev
在继续之前,让我们分解一下我们的依赖项
apollo-server
是一个允许我们在 Node 应用程序中使用 GraphQL 的库。我们将将其用作独立库,但 Apollo 团队还为在 Express、hapi、Fastify 和 Koa 中使用现有 Node Web 应用程序创建了中间件。graphql
包含 GraphQL 语言,并且是apollo-server
的必需对等依赖项。nodemon
是一个有用的库,它将监视我们的项目是否有更改并自动重新启动我们的服务器。
安装完软件包后,接下来让我们创建应用程序的根文件,命名为 index.js
。现在,我们将在该文件中 console.log()
一条消息
console.log("📚 Hello Highlights");
为了简化我们的开发过程,我们将更新 package.json
文件中的 scripts
对象以使用 nodemon
包
"scripts": {
"start": "nodemon index.js"
},
现在,我们可以在终端应用程序中键入 npm start
来启动我们的应用程序。如果一切正常,您将在终端中看到 📚 Hello Highlights
。
GraphQL 模式类型
模式是我们数据和交互的书面表示。通过要求模式,GraphQL 为我们的 API 强制执行严格的计划。这是因为 API 只能返回在模式中定义的数据并执行交互。GraphQL 模式的基本组成部分是对象类型。GraphQL 包含五种内置类型
- 字符串:使用 UTF-8 字符编码的字符串
- 布尔值:真或假值
- 整数:32 位整数
- 浮点数:浮点值
- ID:唯一标识符
我们可以使用这些基本组件构建 API 的模式。在一个名为 schema.js
的文件中,我们可以导入 gql
库并为我们的模式语法准备文件
const { gql } = require('apollo-server');
const typeDefs = gql`
# The schema will go here
`;
module.exports = typeDefs;
要编写我们的模式,我们首先定义类型。让我们考虑一下我们如何为我们的摘录应用程序定义模式。首先,我们将创建一个名为 Highlight
的新类型
const typeDefs = gql`
type Highlight {
}
`;
每个摘录将具有唯一的 ID、一些内容、标题和作者。Highlight
模式将如下所示
const typeDefs = gql`
type Highlight {
id: ID
content: String
title: String
author: String
}
`;
我们可以通过添加感叹号来使其中一些字段成为必需字段
const typeDefs = gql`
type Highlight {
id: ID!
content: String!
title: String
author: String
}
`;
虽然我们已经为我们的摘录定义了一个对象类型,但我们还需要描述客户端如何获取这些数据。这称为 查询
。我们很快将深入探讨查询,但现在让我们在我们的模式中描述某人检索摘录的方式。当请求我们所有的摘录时,数据将作为数组返回(表示为 [Highlight]
),而当我们想要检索单个摘录时,我们将需要传递 ID 作为参数。
const typeDefs = gql`
type Highlight {
id: ID!
content: String!
title: String
author: String
}
type Query {
highlights: [Highlight]!
highlight(id: ID!): Highlight
}
`;
现在,在 index.js
文件中,我们可以导入我们的类型定义并设置 Apollo Server
const {ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const server = new ApolloServer({ typeDefs });
server.listen().then(({ url }) => {
console.log(`📚 Highlights server ready at ${url}`);
});
如果我们一直保持节点进程运行,应用程序将自动更新并重新启动,但如果不是,则在终端窗口中从项目的目录中键入 npm start
将启动服务器。如果我们查看终端,我们应该会看到 nodemon
正在监视我们的文件,并且服务器正在本地端口上运行
[nodemon] 2.0.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
📚 Highlights server ready at http://localhost:4000/
在浏览器中访问 URL 将启动 GraphQL Playground 应用程序,该应用程序提供了一个用于与我们的 API 交互的用户界面。

GraphQL 解析器
虽然我们已经使用初始模式和 Apollo Server 设置开发了我们的项目,但我们还无法与我们的 API 交互。为此,我们将引入解析器。解析器执行其名称暗示的确切操作;它们解析 API 用户请求的数据。我们将通过首先在我们的模式中定义这些解析器,然后在我们的 JavaScript 代码中实现逻辑来编写这些解析器。我们的 API 将包含两种类型的解析器:查询和变异。
让我们首先添加一些要交互的数据。在应用程序中,这通常是我们从数据库中检索和写入的数据,但对于我们的示例,让我们使用一个对象数组。在 index.js
文件中添加以下内容
let highlights = [
{
id: '1',
content: 'One day I will find the right words, and they will be simple.',
title: 'Dharma Bums',
author: 'Jack Kerouac'
},
{
id: '2',
content: 'In the limits of a situation there is humor, there is grace, and everything else.',
title: 'Arbitrary Stupid Goal',
author: 'Tamara Shopsin'
}
]
查询
查询以其所需的格式从 API 请求特定数据。然后,查询将返回一个对象,其中包含 API 用户请求的数据。查询永远不会修改数据;它只访问数据。我们已经在模式中编写了两个查询。第一个返回一个摘录数组,第二个返回一个特定的摘录。下一步是编写将返回数据的解析器。
在 index.js
文件中,我们可以添加一个 resolvers
对象,其中可以包含我们的查询
const resolvers = {
Query: {
highlights: () => highlights,
highlight: (parent, args) => {
return highlights.find(highlight => highlight.id === args.id);
}
}
};
highlights
查询返回完整的摘录数据数组。highlight
查询接受两个参数:parent
和 args
。parent
是 Apollo Server 中任何 GraqhQL 查询的第一个参数,并提供了一种访问查询上下文的方式。args
参数允许我们访问用户提供的参数。在这种情况下,API 用户将提供一个 id 参数来访问特定的摘录。
然后,我们可以更新我们的 Apollo Server 配置以包含解析器
const server = new ApolloServer({ typeDefs, resolvers });
在我们编写完查询解析器并更新 Apollo Server 后,现在可以使用 GraphQL Playground 查询 API 了。要访问 GraphQL Playground,请在您的网络浏览器中访问 http://localhost:4000
。
查询的格式如下所示
query {
queryName {
field
field
}
}
考虑到这一点,我们可以编写一个查询,请求我们每个亮点的 ID、内容、标题和作者
query {
highlights {
id
content
title
author
}
}

假设我们在 UI 中有一个页面,只列出我们突出显示文本的标题和作者。我们不需要检索每个亮点的内容。相反,我们可以编写一个只请求我们所需数据的查询
query {
highlights {
title
author
}
}
我们还编写了一个解析器,通过在查询中包含 ID 参数来查询单个笔记。我们可以按如下方式进行
query {
highlight(id: "1") {
content
}
}

变异
当我们想要修改 API 中的数据时,我们会使用变异。在我们的亮点示例中,我们将需要编写一个变异来创建一个新的亮点,一个更新现有的亮点,以及第三个删除亮点。与查询类似,变异也预期以对象的格式返回结果,通常是执行操作的最终结果。
更新 GraphQL 中任何内容的第一步是编写模式。我们可以通过在我们的 schema.js
文件中添加一个变异类型来在我们的模式中包含变异
type Mutation {
newHighlight (content: String! title: String author: String): Highlight!
updateHighlight(id: ID! content: String!): Highlight!
deleteHighlight(id: ID!): Highlight!
}
我们的 newHighlight
变异将采用内容的必需值以及可选的 title
和 author
值,并返回一个 Highlight
。updateHighlight
变异将要求将亮点 id
和 content
作为参数值传递,并将返回更新后的 Highlight
。最后,deleteHighligh
t 变异将接受一个 ID 参数,并将返回已删除的 Highlight。
模式更新为包含变异后,我们现在可以更新 index.js
文件中的 resolvers
以执行这些操作。每个变异都将更新我们的 highlights
数据数组。
const resolvers = {
Query: {
highlights: () => highlights,
highlight: (parent, args) => {
return highlights.find(highlight => highlight.id === args.id);
}
},
Mutation: {
newHighlight: (parent, args) => {
const highlight = {
id: String(highlights.length + 1),
title: args.title || '',
author: args.author || '',
content: args.content
};
highlights.push(highlight);
return highlight;
},
updateHighlight: (parent, args) => {
const index = highlights.findIndex(highlight => highlight.id === args.id);
const highlight = {
id: args.id,
content: args.content,
author: highlights[index].author,
title: highlights[index].title
};
highlights[index] = highlight;
return highlight;
},
deleteHighlight: (parent, args) => {
const deletedHighlight = highlights.find(
highlight => highlight.id === args.id
);
highlights = highlights.filter(highlight => highlight.id !== args.id);
return deletedHighlight;
}
}
};
编写这些变异后,我们可以使用 GraphQL Playground 来练习修改数据。变异的结构与查询几乎相同,指定变异的名称,传递参数值,并请求返回特定的数据。让我们首先添加一个新的亮点
mutation {
newHighlight(author: "Adam Scott" title: "JS Everywhere" content: "GraphQL is awesome") {
id
author
title
content
}
}

然后,我们可以编写变异来更新亮点
mutation {
updateHighlight(id: "3" content: "GraphQL is rad") {
id
content
}
}
以及删除亮点
mutation {
deleteHighlight(id: "3") {
id
}
}
总结
恭喜!您现在已成功构建了一个 GraphQL API,使用 Apollo Server,并且可以对内存中的数据对象运行 GraphQL 查询和变异。我们已经为探索 GraphQL API 开发的世界奠定了坚实的基础。
以下是一些提高水平的潜在下一步
- 了解嵌套的 GraphQL 查询和变异。
- 按照Apollo 全栈教程进行操作。
- 更新示例以包含数据库,例如 MongoDB 或 PostgreSQL。
- 探索更多优秀的 CSS-Tricks GraphQL 文章。
- 利用您新获得的 GraphQL 知识来使用 Gatsby 构建静态网站。
看起来不错且简单……还有没有更多关于高级多对多关系查询示例的内容?
FaunaDB 的文档 提供了一些关于各种关系的优秀示例。例如,“多对多”在您的模式中可能看起来像这样。
一个非常棒的介绍。非常感谢。
我很高兴您觉得它有帮助!
感谢您的精彩课程,我工作中的开发人员使用它们,但我没有时间学习!
不过我发现了一个问题:
addHighlight
变异不能保证创建的id
是唯一的。如果我有{1, 2, 3, 4, 5}
然后删除 3,然后添加一个新的,新的 id 将是{1, 2, 4, 5}.lenght + 1
,即 4 + 1 = 5,因此我将有{1, 2, 4, 5, 5}
。我只是吹毛求疵,这不是您主题的重点。
感谢 @Thoscellen!您绝对是对的,为了演示的简单性,这些创建的 id 并不保证是唯一的。在生产应用程序中,我建议使用uuid 或数据库生成的 ID。希望这有助于澄清!
真是太棒的介绍了,老兄,但我们现在应该开始构建 GraphQL API 还是继续使用 REST 并最终迁移到 GraphQL?
谢谢 Pablo!我认为这个问题没有唯一的答案,它对每种情况都是独一无二的。整个开发社区都更熟悉 REST,因此它仍然是开放 API、与大型团队合作以及简单 CRUD 应用程序的绝佳选择。GraphQL 也可以用作 REST API 的包装器,这意味着您可能不必做出选择。
这是对 GraphQL 的精彩简短解释,是否可以将其用于嵌套类型/json。例如,帖子有许多评论
谢谢 Kiran!是的,这是可能的(也是 GraphQL 的一个惊人功能)获得嵌套响应。
终于,我找到了一篇让我开始使用 GraphQL 的教程。感谢 CSS-Tricks