写在最前面
大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。
为了提高大家的阅读体验,对语句的结构和内容略有调整。如果发现本文中有存在瑕疵的地方,或者你有任何意见或者建议,可以在评论区留言,或者加我的微信:code
_maomao
,欢迎相互沟通交流学习。
(σ゚∀゚)σ..:
*☆哎哟不错哦
第4章 我们的第一个GraphQL API
根据推测,如果你正在阅读本文,那么你就是一个人。作为人类,你有很多兴趣和爱好,你也有家人、朋友、熟人、同学和同事。这些人也有自己的社会关系、兴趣和爱好。这些关系和兴趣中有些重叠,有些没有。总而言之,我们每个人都有我们生活中人的关系图。这些类型的互连数据正是GraphQL
最初提出要在API
开发中解决的挑战。
通过编写GraphQL API
,我们能够高效地连接数据,从而降低了复杂性和请求数量,同时使我们能够准确地为客户提供所需的数据。听起来对Notes
应用程序来说有点过了,是吗?也许是吧,但是正如你将看到的,GraphQL JavaScript
生态系统提供的工具和技术都可以启用和简化所有类型的API
开发。
在本章中,我们将使用apollo-server-express
包来构建GraphQL API
。为此,我们将探究GraphQL
的基本主题,编写GraphQL
模式,编写代码以解析模式功能,并使用GraphQL Playground
用户界面访问API
。
将我们的服务器转换为API(排序)
让我们通过使用以下命令将Express
服务器变成GraphQL
服务器来开始API
开发。
apollo-server-express
软件包。
Apollo Server
是一个开源GraphQL
服务器库,可与大量Node.js
服务器框架一起使用,包括Express
,Connect
,Hapi
和Koa
。
GraphQL API
,可从Node.js
应用程序中获取数据,还提供了有用的工具,例如GraphQL Playground
,一个可视化的辅助器,帮助我们在开发中查看API
。
要编写我们的API
,我们将修改上一章中编写的Web
应用程序的代码。让我们首先开始于apollo-server-express
软件包。
将以下内容添加到src/index.js
文件的顶部:
const { ApolloServer, gql } = require('apollo-server-express');
现在我们已经导入了apollo
服务器,我们将建立一个基本的GraphQL
应用程序。
GraphQL
应用程序由两个主要组件组成:类型定义和解析器,用于解析针对数据执行的查询和修改。
这听起来像是胡话,但没关系。
我们将实现“ Hello World
” API
响应,并将在我们API
的整个开发过程中进一步探讨这些GraphQL
主题。
首先,让我们构建一个基本模式,将其存储在一个名为typeDefs
的变量中。
该模式将描述一个名为hello
的查询,该查询将返回一个字符串:
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`;
现在我们已经设置了结构,我们可以添加一个解析器,该解析器将向用户返回一个值。这只是一个简单的函数,返回字符串“ Hello world
!”:
// Provide resolver functions for our schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!'
}
};
最后,我们将集成Apollo Server
以提供GraphQL API
。
为此,我们将添加一些特定于Apollo Server
的设置和中间件,并更新我们的应用程序。
监听代码:
// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });
// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });
app.listen({ port }, () =>
console.log(
`GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
)
);
综上,我们的src/index.js
文件现在应如下所示:
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
// Run the server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;
// Construct a schema, using GraphQL's schema language
const typeDefs = gql`
type Query {
hello: String
}
`;
// Provide resolver functions for our schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!'
}
};
const app = express();
// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });
// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });
app.listen({ port }, () =>
console.log(
`GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
)
);
如果你没有运行nodemon
进程,则可以直接进入浏览器。否则,你必须在终端应用程序中输入npm
运行dev
以启动服务器。然后访问http://localhost:4000/api
,在这里你会看到GraphQL Playground
(图4-1
)。
该Web
应用程序与Apollo Server
捆绑在一起,是使用GraphQL
的巨大好处之一。从这里,你可以运行GraphQL
查询和修改并查看结果。
你也可以单击“Schema
”选项卡来访问为API
自动创建的文档。
图4-1
。GraphQL Playground
注意
GraphQL Playground
具有深色的默认语法主题。在整本书中,我将使用“浅色”主题以提高对比度。
这可以在GraphQL Playground
的设置中进行配置,可以通过单击齿轮图标进行访问。现在,我们可以针对我们的GraphQL API
编写查询。为此,在GraphQL Playground
中键入以下内容:
query {
hello
}
当你单击“播放”按钮时,查询应返回以下内容(图4-2
):
{
"data": {
"hello": "Hello world!"
}
}
图4-2
。你好查询
就是这样!现在,我们已经可以通过GraphQL Playground
访问有效的GraphQL API
。我们的API
会查询hello
,并返回字符串Hello world
!。
更重要的是,我们现在具有了功能构建齐全的API
的结构。
GraphQL基础
在上一节中,我们深入探讨并开发了我们的第一个API
,先让我们花一些时间后退一步来看看GraphQL API
的不同部分。
GraphQL API
的两个主要构建模块是模式和解析器。
通过理解这两个组件,可以更有效地将它们应用于API
设计和开发。
Schemas
模式是我们的数据和交互的书面表示。
通过请求一个模式,GraphQL
规范了我们的API
请求。
这是因为你的API
只能返回数据并执行架构中定义的交互。
GraphQL
模式的基本组成部分是对象类型。
在前面的示例中,我们创建了一个GraphQL
对象类型的Query
,带有一个hello
字段,该对象返回了标准的String
类型。
GraphQL
包含五种内置标量类型:
- 串
:String
具有
UTF-8
字符编码的字符串 - 布尔型
Boolean
正确或错误的值
- 整数
Int
32
位整数 - 浮点型
Flaot
浮点值
ID
唯一标识符
使用这些基本组件,我们可以为API
构建一个模式。
我们首先定义类型。假设我们正在为披萨菜单创建一个API
。
这时,我们可以定义披萨的GraphQL
模式类型,如下所示:
type Pizza {
}
现在,每个披萨饼都有唯一的ID
,大小(例如小,中或大),切片数和可选的浇头。
Pizza
模式可能看起来像这样:
type Pizza {
id: ID
size: String
slices: Int
toppings: [String]
}
在此架构中,某些字段值是必需的(例如ID
,大小和切片),而其他字段值则是可选的(例如配料)。我们可以使用感叹号来表示字段必须包含一个值。
让我们更新结构以表示所需的值:
type Pizza {
id: ID!
size: String!
slices: Int!
toppings: [String]
}
在本书中,我们将编写一个基本模式,这将使我们能够执行常见API
中的绝大多数操作。如果你想探索所有的GraphQL
模式选项,建议你阅读GraphQL
模式文档。
解析器Resolvers
GraphQL API
的第二部分是解析器。
解析程序完全执行其名称所暗示的操作;他们解析API
然后获取用户已请求的数据。
我们将首先在结构中定义这些解析器,然后在JavaScript
代码中实现逻辑,以编写这些解析器。
我们的API
将包含两种类型的解析器:查询和修改。
查询
查询从API
请求中获取所需格式的特定数据。
在我们假设的披萨API
中,我们可以编写一个查询,该查询将返回菜单上披萨的完整列表,而另一个查询将返回有关单个披萨的详细信息。然后查询将返回一个对象,其中包含API
中用户请求的数据。查询从不修改数据,仅访问数据。
修改
当我们想要修改API
中的数据时,我们使用一个修改。
在我们的披萨示例中,我们可能编写了一个修改,该修改可以更改给定披萨的配料,而另一个修改则可以调整切片数。
与查询类似,也期望修改以对象的形式返回结果,通常是所执行操作的最终结果。
调整我们的API
现在你已经对GraphQL
的组件有了很好的了解,让我们为Notes
应用程序调整我们的初始API
代码。
首先,我们将编写一些代码来阅读和创建笔记。我们需要做的第一件事是为API
提供一些数据。让我们创建一个“笔记”对象数组,将其用作API
提供的基本数据。随着项目的发展,我们将用数据库替换这个数据。
现在,我们将数据存储在一个名为notes
的变量中。数组中的每个笔记将是一个具有三个属性(id
,content
和author
)的对象:
let notes = [
{ id: '1', content: 'This is a note', author: 'Adam Scott' },
{ id: '2', content: 'This is another note', author: 'Harlow Everly' },
{ id: '3', content: 'Oh hey look, another note!', author: 'Riley Harrison' }
];
现在我们有了一些数据,我们将调整我们的GraphQL API
来使用它。
让我们从关注我们的模式开始。我们的模式是GraphQL
对我们的数据以及如何与之交互的表示。已知我们会有笔记,这些笔记将被查询和修改。这些笔记目前将包含ID
、内容和作者3
个字段。
让我们在typeDefs GraphQL
模式中创建一个相应的笔记类型。
以下表示我们API
中笔记的属性:
type Note {
id: ID!
content: String!
author: String!
}
现在,让我们添加一个查询,该查询将允许我们检索所有笔记的列表。
让我们更新一个笔记查询,这将返回笔记对象的数组:
type Query {
hello: String!
notes: [Note!]!
}
现在,我们可以更新解析器代码以执行返回数据数组的工作。
让我们更新包括以下笔记解析器的查询代码,该解析器返回原始数据对象:
Query: {
hello: () => 'Hello world!',
notes: () => notes
},
如果现在切换到运行在http://localhost:4000/api
的GraphQL Playground
,我们可以测试笔记查询。
为此,请键入以下查询:
query {
notes {
id
content
author
}
}
然后,当你单击“播放”按钮时,应该看到返回的数据对象,其中包含数据数组(图4-3
)。
图4-3
。笔记查询。
在尝试GraphQL
最酷的方面之一是我们可以删除任何请求的字段,例如id
或author
。当我们这样做时,API
精确地返回我们所请求的数据。
这样,使用数据的客户端可以控制每个请求中发送的数据量,并将该数据限制在所需的范围内(图4-4
)。
图4-4
。笔记查询,仅请求内容数据。
现在我们可以查询完整的笔记列表,让我们编写一些代码,使我们可以查询单个笔记。
从用户界面的角度,你可以想象这样做的用处,可以显示包含单个特定笔记的视图。为此,我们希望请求带有特定id
值的笔记。这要求我们使用GraphQL
模式中的参数。参数允许API
使用者将特定的值传递给解析器函数,从而提供解析所需的信息。让我们添加一个笔记查询,该查询将使用id
类型的ID
作为参数。
我们将typeDefs
中的Query
对象更新为以下内容,其中包括新的笔记查询:
type Query {
hello: String
notes: [Note!]!
note(id: ID!): Note!
}
更新结构后,我们已经可以编写在查询解析器后返回的笔记了。现在我们需要能够读取API
用户的参数值。
有用信息
Apollo Server
将以下有用的参数传递给我们的解析器功能:
parent
父查询的结果,在嵌套查询时很有用。
args
这些是用户在查询中传递的参数。
context
信息从服务器应用程序传递到解析器功能。
这可能包括诸如当前用户或数据库信息之类的信息。
info
有关查询本身的信息。
我们将根据需要在代码中进一步探索这些内容。如果你感到好奇,可以在Apollo
服务器的文档中了解有关这些参数的更多信息。
现在,我们仅需要第二个参数args
中包含的信息。
笔记查询将笔记ID
作为参数,在我们的笔记对象数组中找到它。将以下内容添加到查询解析器代码中:
note: (parent, args) => {
return notes.find(note => note.id === args.id);
}
解析器代码现在应如下所示:
const resolvers = {
Query: {
hello: () => 'Hello world!',
notes: () => notes,
note: (parent, args) => {
return notes.find(note => note.id === args.id);
}
}
};
运行我们的查询,让我们返回到Web
浏览器并访问位于http://localhost:4000/api
的GraphQL Playground
。
现在,我们可以查询具有特定ID
的笔记,如下所示:
query {
note(id: "1") {
id
content
author
}
}
运行此查询时,你应该收到带有请求的id
值的笔记的结果。如果你尝试查询不存在的笔记,则应收到结果为null
的结果。要对此进行测试,请尝试更改id
值以返回不同的结果。
让我们通过使用GraphQL
修改引入创建新笔记的功能来强化我们的初始API
代码。在这种修改中,用户将传递笔记的内容。
现在,我们将对笔记的作者进行编码。
让我们首先使用Mutation
类型更新我们的typeDefs
模式,我们将其称为newNote
:
type Mutation {
newNote(content: String!): Note!
}
现在,我们将编写一个修改解析器,它将笔记内容作为参数,将笔记存储为对象,然后将其添加到notes
数组中。我们将Mutation
对象添加到解析器。
在Mutation
对象中,我们将添加一个名为newNote
的函数,该函数具有parent
和args
参数。在此函数中,我们将使用参数content
并创建一个具有id
,content
和author
键的对象。
你可能已经注意到,这与笔记的当前模式匹配。然后,我们将该对象加入到notes
数组并返回该对象。返回的对象允许GraphQL
修改接收预期格式的响应。
继续并编写以下代码:
Mutation: {
newNote: (parent, args) => {
let noteValue = {
id: String(notes.length + 1),
content: args.content,
author: 'Adam Scott'
};
notes.push(noteValue);
return noteValue;
}
}
我们的src/index.js
文件现在将如下所示:
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
// Run our server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;
let notes = [
{ id: '1', content: 'This is a note', author: 'Adam Scott' },
{ id: '2', content: 'This is another note', author: 'Harlow Everly' },
{ id: '3', content: 'Oh hey look, another note!', author: 'Riley Harrison' }
];
// Construct a schema, using GraphQL's schema language
const typeDefs = gql`
type Note {
id: ID!
content: String!
author: String!
}
type Query {
hello: String
notes: [Note!]!
note(id: ID!): Note!
}
type Mutation {
newNote(content: String!): Note!
}
`;
// Provide resolver functions for our schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
notes: () => notes,
note: (parent, args) => {
return notes.find(note => note.id === args.id);
}
},
Mutation: {
newNote: (parent, args) => {
let noteValue = {
id: String(notes.length + 1),
content: args.content,
author: 'Adam Scott'
};
notes.push(noteValue);
return noteValue;
}
}
};
const app = express();
// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });
// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });
app.listen({ port }, () =>
console.log(
`GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
)
);
更新结构和解析器以接受修改后,让我们在GraphQL Playground
的http://localhost:4000/api
上进行尝试。
在Playground
上,单击+号以创建一个新选项卡,并按如下所示编写变量:
mutation {
newNote (content: "This is a mutant note!") {
content
id
author
}
}
当你单击“播放”按钮时,你应该会收到包含新笔记的内容、ID
和作者的结果。
你还可以通过重新运行notes
查询来查看该修改是否起作用。
为此,请切换回包含该查询的GraphQL Playground
选项卡,或键入以下内容:
query {
notes {
content
id
author
}
}
运行此查询时,你现在应该看到四个笔记,包括最近添加的笔记。
数据存储
我们将数据存储在内存中。这意味着,只要我们重新启动服务器,就会丢失该数据。在下一章中,我们将使用数据库来持久化数据。
现在,我们已经成功实现了查询和修改解析器,并在GraphQL Playground
用户界面中对其进行了测试。
结论
在本章中,我们已经使用apollo-server-express
模块成功构建了GraphQL API
。现在,我们可以对内存中的数据对象运行查询和修改。此设置为我们提供了构建任何API
的坚实基础。在下一章中,我们将探讨使用数据库持久化数据的能力。
如果有理解不到位的地方,欢迎大家纠错。如果觉得还可以,麻烦您点赞收藏或者分享一下,希望可以帮到更多人。