GraphQL 变更如何修改数据
我们已经为 解析器需要适用于 查询 操作的模式 字段编写了所有。现在,让我们为我们模式的 变更编写解析器。这一过程几乎完全相同。
login
首先,让我们为 解析器 编写 Mutation.login
,它使用户能够登录我们的应用程序。在 解析器 下方的 Query
字段 添加以下代码:
// Query: {// ...// },Mutation: {login: async (_, { email }, { dataSources }) => {const user = await dataSources.userAPI.findOrCreateUser({ email });if (user) {user.token = Buffer.from(email).toString('base64');return user;}},},
此 解析器 获取 email
地址并从我们的 userAPI
返回相应用户数据。我们向对象添加 token
字段 以表示用户的活动会话。在后面的章节中,我们将了解如何将此返回用户数据持久存储在我们的应用程序客户端中。
对已登录用户进行身份验证
示例应用程序中使用的认证方法一点也不安全并且不应在实际应用程序中使用。但是,您可以将下方介绍的原则应用到 是 安全的令牌认证方法中。
解析器 返回的 User
对象包含 token
,客户端可使用该 token
对其自身在我们服务器上进行认证。现在,我们需要向我们的服务器添加逻辑以实际执行认证。
在 src/index.js
中,导入 isEmail
函数并将 context
函数传递给 ApolloServer
构造函数,与以下内容相符:
const isEmail = require("isemail");const server = new ApolloServer({context: async ({ req }) => {// simple auth check on every requestconst auth = (req.headers && req.headers.authorization) || "";const email = Buffer.from(auth, "base64").toString("ascii");if (!isEmail.validate(email)) return { user: null };// find a user by their emailconst users = await store.users.findOrCreate({ where: { email } });const user = (users && users[0]) || null;return { user: { ...user.dataValues } };},// Additional constructor options});
上文定义的 context
函数在 对我们服务器发送的每个 GraphQL 操作 时会调用一次。此函数的返回值将会成为 context
参数,其作为该 操作 的一部分运行时的每个 解析器 都已经传递给该参数。
你可能已经注意到 dataSources
即使我们希望其成为 context
在我们 查询 解析器 中的 参数 的一部分,但它并未出现在任何地方,这是因为尽管我们是在 context
外部定义的 dataSources
,它已 自动包含 于每个 操作。
这是我们的 context
函数的功能:
- 获取
Authorization
标头(如果存在)的值,该标头包含在传入的请求中。 - 解码
Authorization
标头值。 - 如果解码后的值类似于电子邮件地址,请从数据库中获取该电子邮件地址的用户详细信息,并返回一个对象,其
user
字段 中包含这些详细信息。
通过在每次context
操作的开始创建一个context
对象,我们的所有解析器都可以访问已登录用户的详细信息并执行专门为该用户采取的操作。
bookTrips
和 cancelTrip
现在回到resolvers.js
,让我们为bookTrips
和cancelTrip
添加到Mutation
对象。
//Mutation: {// login: ...bookTrips: async (_, { launchIds }, { dataSources }) => {const results = await dataSources.userAPI.bookTrips({ launchIds });const launches = await dataSources.launchAPI.getLaunchesByIds({launchIds,});return {success: results && results.length === launchIds.length,message:results.length === launchIds.length? 'trips booked successfully': `the following launches couldn't be booked: ${launchIds.filter(id => !results.includes(id),)}`,launches,};},cancelTrip: async (_, { launchId }, { dataSources }) => {const result = await dataSources.userAPI.cancelTrip({ launchId });if (!result)return {success: false,message: 'failed to cancel trip',};const launch = await dataSources.launchAPI.getLaunchById({ launchId });return {success: true,message: 'trip cancelled',launches: [launch],};},
若要匹配我们的模式,这两个解析器都返回一个对象,其符合TripUpdateResponse
类型的结构。此类型的字段包括success
指示器、状态message
和mutation预订或取消的launches
数组。
该bookTrips
解析器需要考虑部分成功的可能性,其中一些launches成功预订而另一些失败。上面的代码在message
字段中指示部分成功。
运行测试 mutations
我们准备测试我们的mutations!返回Apollo Sandbox。
获取登录令牌
GraphQL 突变的结构与查询完全相同,只是它们使用了 mutation
关键字。粘贴以下 突变并运行它:
mutation LoginUser {token}}
服务器将以如下方式做出响应
"data": {"login": {"token": "ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20="}}
token
字段的值是我们的登录令牌(也就是我们提供的电子邮件地址的 Base64 编码)。我们将在下一个 突变中使用此值。
预订旅行
让我们尝试预订一些旅行。只有经过身份验证的用户才能预订旅行,因此我们将把我们的登录令牌包含在请求中。
首先,粘贴以下 突变到工具的 查询编辑器:
mutation BookTrips {bookTrips(launchIds: [67, 68, 69]) {successmessagelaunches {id}}}
接下来,使用以下值在 标头选项卡中设置标头(在 查询编辑器下方面板中的一个单独文本区域)。我们提供了 标头密钥
和 值
供你复制:
Authorization
'ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20='
运行 突变。你应该会看到一条成功消息,以及我们刚刚预订的旅行的 id
。
像这样手动运行 突变是测试我们的 API 的一种有用方式,但是一个真实世界的 图需要额外的工具来确保它能够安全地增长和变化。在下一部分,我们将我们的服务器连接到 Apollo Studio 以激活该工具。