翻译 | 《JavaScript Everywhere》第8章 用户操作(^_^)
写在最后面
大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。
为了进步大家的浏览体验,对语句的构造和内容略有调整。如果发现本文中有存在瑕疵的中央,或者你有任何意见或者倡议,能够在评论区留言,或者加我的微信:code\_maomao,欢送互相沟通交流学习。
(゚∀゚)..:\*☆哎哟不错哦
第8章 用户操作
想像你刚刚退出了一个俱乐部(还记得“超级酷人机密俱乐部”吗?),当你第一次呈现时,没有任何事可做。俱乐部是一个空阔的大房间,人们进进出出,无奈与俱乐部的其他人产生互动。我有点外向,所以这听起来还不错,然而我不违心为此领取会员费。
当初,咱们的API
实质上是一个宏大的无用俱乐部。咱们有创立数据的办法和用户登录的办法,然而没有任何一种办法容许用户领有该数据。在本章中,咱们将通过增加用户交互来解决这个问题。咱们将编写代码,使用户可能领有本人创立的笔记,限度能够删除或批改笔记的人员,并容许用户“珍藏”他们喜爱的笔记。此外,咱们将使API
用户可能进行嵌套查问,从而使咱们的UI
能够编写将用户与笔记相关联的简略查问。
开始之前
在本章中,咱们将对Notes
文件进行一些相当重要的更改。因为咱们数据库中的数据量很少,可能你会发现从本地数据库中删除现有笔记更容易。这不是必须的,然而能够缩小你在浏览本章时的困惑。
为此,咱们将进入MongoDB shell
,确保援用了notedly
的数据库(数据库名称在.env
文件中),并应用MongoDB
的 .remove
()办法。
在终端中,键入以下内容:
$ mongo$ use notedly$ db.notes.remove({})
用户增加新笔记
在上一章中,咱们更新了src/index.js
文件,以便当用户发出请求时查看JWT
。如果token
令牌存在,咱们将对其进行解码并将以后用户增加到咱们的GraphQL
上下文中。这使咱们能够将用户信息发送到咱们调用的每个解析器函数。咱们将更新现有的GraphQL
申请以验证用户信息。为此,咱们将利用Apollo Server
的AuthenticationError
和ForbiddenError
办法,这将容许咱们引发适量的谬误。这些将帮忙咱们调试开发过程以及向客户端发送适和的响应。
在开始之前,咱们须要将mongoose
包导入咱们的mutations.js
解析器文件中。这将使咱们可能适当地调配穿插援用的MongoDB
对象ID
字段。更新src/resolvers/mutation.js
顶部的模块导入,如下所示:
const mongoose = require('mongoose');
当初,在咱们的newNote
申请中,咱们将用户设置为函数参数,而后查看是否将用户传递给函数。
如果找不到用户ID
,咱们将抛出AuthenticationError
,因为必须登录到咱们的服务能力创立新笔记。确认已通过身份验证的用户发出请求后,就能够在数据库中创立笔记。为此,咱们当初将为作者调配传递给解析器的用户ID
。这将使咱们可能从笔记本身中援用创立用户。
在src/resolvers/mutation.js
,增加以下内容:
// add the users contextnewNote: async (parent, args, { models, user }) => { // if there is no user on the context, throw an authentication error if (!user) { throw new AuthenticationError('You must be signed in to create a note'); } return await models.Note.create({ content: args.content, // reference the author's mongo id author: mongoose.Types.ObjectId(user.id) });},
最初一步是将穿插援用利用于咱们数据库中的数据。为此,咱们将须要更新MongoDB notes
构造的author
字段。在/src/models/note.js
,如下更新作者字段:
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true}
有了此构造,所有新笔记都将精确记录并从申请的上下文中援用的作者。让咱们在GraphQL Playground
中编写一个newNote
申请来尝试一下:
mutation { newNote(content: "Hello! This is a user-created note") { id content }}
编写申请时,咱们还必须确保在Authorization
标头中传递JWT
(请参见图8-1
):
{ "Authorization": "<YOUR_JWT>" }
如何检索JWT
如果你没有JWT
,能够执行signIn
批改来检索。
图8-1
。GraphQL Playground
中的newNote
申请。
目前,咱们的API
不会返回作者信息,然而咱们能够通过在MongoDB shell
中查找笔记来验证是否正确增加了作者。在终端窗口中,键入以下内容:
mongodb.notes.find({_id: ObjectId("A DOCUMENT ID HERE")})
返回的值应包含作者密钥,并带有对象ID
的值。
用户更新和删除权限
当初,咱们还能够将用户查看增加到咱们的deleteNote
和updateNote
申请中。这些将要求咱们既查看是否将用户传递到上下文,又查看该用户是否是笔记的所有者。为此,咱们将查看存储在数据库的author
字段中的用户ID
是否与传递到解析器上下文中的用户ID
相匹配。
在src/resolvers/mutation.js
,如下更新deleteNote
申请:
deleteNote: async (parent, { id }, { models, user }) => { // if not a user, throw an Authentication Error if (!user) { throw new AuthenticationError('You must be signed in to delete a note'); } // find the note const note = await models.Note.findById(id); // if the note owner and current user don't match, throw a forbidden error if (note && String(note.author) !== user.id) { throw new ForbiddenError("You don't have permissions to delete the note"); } try { // if everything checks out, remove the note await note.remove(); return true; } catch (err) { // if there's an error along the way, return false return false; }},
当初,也在src/resolvers/mutation.js
,如下更新updateNote
申请:
updateNote: async (parent, { content, id }, { models, user }) => { // if not a user, throw an Authentication Error if (!user) { throw new AuthenticationError('You must be signed in to update a note'); } // find the note const note = await models.Note.findById(id); // if the note owner and current user don't match, throw a forbidden error if (note && String(note.author) !== user.id) { throw new ForbiddenError("You don't have permissions to update the note"); } // Update the note in the db and return the updated note return await models.Note.findOneAndUpdate( { _id: id }, { $set: { content } }, { new: true } );},
用户查问
通过更新现有的申请包含用户查看,咱们还增加一些特定于用户的查问。
为此,咱们将增加三个新查问:
user
给定特定的用户名,返回用户的信息
users
返回所有用户的列表
me
返回以后用户的用户信息
在编写查问解析器代码之前,请将这些查问增加到GraphQL src/schema.js
文件,如下所示:
type Query { ... user(username: String!): User users: [User!]! me: User!}
当初在src/resolvers/query.js
文件,编写以下解析程序查问代码:
module.exports = { // ... // add the following to the existing module.exports object: user: async (parent, { username }, { models }) => { // find a user given their username return await models.User.findOne({ username }); }, users: async (parent, args, { models }) => { // find all users return await models.User.find({}); }, me: async (parent, args, { models, user }) => { // find a user given the current user context return await models.User.findById(user.id); }}
让咱们看看这些在咱们的GraphQL Playground
中的构造。首先,咱们能够编写一个用户查问来查找特定用户的信息。确保应用曾经创立的用户名:
query { user(username:"adam") { username email id }}
这将返回一个数据对象,其中蕴含指定用户的用户名、电子邮件和ID
值(图8-2
)。
图8-2
。GraphQL Playground
中的用户查问
当初要查询数据库中的所有用户,咱们能够应用users
查问,这将返回一个蕴含所有用户信息的数据对象(图8-3
):
query { users { username email id }}
图8-3
。用户在GraphQL Playground
中查问
当初,咱们能够应用传递给HTTP
标头的JWT
,通过me
查问来查找无关登录用户的信息。
首先,请确保在GraphQL Playground
的HTTP
标头局部中蕴含令牌:
{ "Authorization": "<YOUR_JWT>" }
当初,像这样执行me
查问(图8-4
):
query { me { username email id }}
图8-4
。GraphQL Playground
中的me
查问
有了这些解析器后,咱们当初能够查问API
以取得用户信息。
切换笔记收藏夹
咱们还有最初一项性能能够增加到咱们的用户交互中。你可能还记得咱们的应用程序标准指出:“用户将可能珍藏其余用户的笔记,并检索他们的收藏夹列表。相似于Twitter
的“心”和Facebook
的“喜爱”,咱们心愿用户可能将笔记标记(或勾销标记)为珍藏。为了实现此行为,咱们将批改遵循GraphQL
模式的规范模式,而后是数据库模块,最初是resolver
函数。
首先,咱们将在/src/schema.js
中更新GraphQL
模式。通过向咱们的Note
类型增加两个新属性来实现。 favoriteCount
将跟踪笔记收到的“收藏夹”总数。 favoritedBy
将蕴含一组喜爱笔记的用户。
type Note { // add the following properties to the Note type favoriteCount: Int! favoritedBy: [User!]}
咱们还将增加收藏夹列表到咱们的用户类型:
type User { // add the favorites property to the User type favorites: [Note!]! }
接下来,咱们将/src/schema.js
在中增加一个申请,称为toggleFavorite
,它将增加或删除指定笔记的收藏夹。此申请以笔记ID
作为参数,并返回指定的笔记。
type Mutation { // add toggleFavorite to the Mutation type toggleFavorite(id: ID!): Note!}
接下来,咱们须要更新笔记模块,在数据库中包含favoriteCount
和favoritedBy
属性。 最喜爱的数字将是一个数字类型,默认值为0
。 最喜爱的数字将是一个对象数组,其中蕴含对咱们数据库中用户对象ID
的援用。咱们残缺的/src/models/note.js
文件如下所示:
const noteSchema = new mongoose.Schema( { content: { type: String, required: true }, author: { type: String, required: true }, // add the favoriteCount property favoriteCount: { type: Number, default: 0 }, // add the favoritedBy property favoritedBy: [ { type: mongoose.Schema.Types.ObjectId, ref: 'User' } ] }, { // Assigns createdAt and updatedAt fields with a Date type timestamps: true });
随着咱们的GraphQL
模式和数据库模块的更新,咱们能够编写toggleFavorite
申请。此申请将收到一个笔记ID
作为参数,并检查用户是否已被列在“ favouritedBy
”数组中。如果列出了该用户,咱们将通过缩小favoriteCount
并从列表中删除该用户来删除收藏夹。如果用户尚未收藏该笔记,则咱们将favouriteCount
减少1
,而后将以后用户增加到favouritedBy
数组中。为此,请将以下代码增加到src/resolvers/mutation.js
文件:
toggleFavorite: async (parent, { id }, { models, user }) => { // if no user context is passed, throw auth error if (!user) { throw new AuthenticationError(); } // check to see if the user has already favorited the note let noteCheck = await models.Note.findById(id); const hasUser = noteCheck.favoritedBy.indexOf(user.id); // if the user exists in the list // pull them from the list and reduce the favoriteCount by 1 if (hasUser >= 0) { return await models.Note.findByIdAndUpdate( id, { $pull: { favoritedBy: mongoose.Types.ObjectId(user.id) }, $inc: { favoriteCount: -1 } }, { // Set new to true to return the updated doc new: true } ); } else { // if the user doesn't exist in the list // add them to the list and increment the favoriteCount by 1 return await models.Note.findByIdAndUpdate( id, { $push: { favoritedBy: mongoose.Types.ObjectId(user.id) }, $inc: { favoriteCount: 1 } }, { new: true } ); }},
应用此代码后,让咱们测试一下在GraphQL Playground
中切换喜爱的笔记的性能。让咱们用一个新创建的笔记来做到这一点。咱们将首先编写一个newNote
申请,确保蕴含一个带有无效JWT
的Authorization
标头(图8-5
):
mutation { newNote(content: "Check check it out!") { content favoriteCount id }}
图8-5
。一个newNote
申请
你会留神到该新笔记的收藏夹计数主动设置为0
,因为这是咱们在数据模块中设置的默认值。当初,让咱们编写一个toggleFavorite
申请''以将其标记为珍藏,将笔记的ID
作为参数传递。同样,请确保包含带有无效JWT
的Authorization HTTP
标头。
mutation { toggleFavorite(id: "<YOUR_NOTE_ID_HERE>") { favoriteCount }}
运行此批改后,笔记的favoriteCount
的值应为1
。如果从新运行该申请,则favoriteCount
将缩小为0
(图8-6
)。
图8-6
。toggleFavorite
批改
用户当初能够在收藏夹中标记和勾销标记笔记。更重要的是,我心愿该性能能够演示如何向GraphQL
应用程序的API
增加新性能。
嵌套查问
GraphQL
的一大长处是咱们能够嵌套查问,使咱们能够编写单个查问来准确返回所需的数据,而不是用多个查问。
咱们的用户类型的GraphQL
模式包含以数组格局列出作者的笔记列表,而咱们的笔记类型包含对其作者的援用。所以,咱们可用于从用户查问中提取笔记列表,或从笔记查问中获取作者信息。
这意味着咱们能够编写如下查问:
query { note(id: "5c99fb88ed0ca93a517b1d8e") { id content # the information about the author note author { username id } }}
如果当初咱们尝试运行相似于上一个查问的嵌套查问,则会收到谬误音讯。这是因为咱们尚未编写用于对此信息执行数据库查找的解析程序代码。要启用此性能,咱们将在src/resolvers
目录中增加两个新文件。
在src/resolvers/note.js
,增加以下内容:
module.exports = { // Resolve the author info for a note when requested author: async (note, args, { models }) => { return await models.User.findById(note.author); }, // Resolved the favoritedBy info for a note when requested favoritedBy: async (note, args, { models }) => { return await models.User.find({ _id: { $in: note.favoritedBy } }); }};
在src/resolvers/user.js
,增加以下内容:
module.exports = { // Resolve the list of notes for a user when requested notes: async (user, args, { models }) => { return await models.Note.find({ author: user._id }).sort({ _id: -1 }); }, // Resolve the list of favorites for a user when requested favorites: async (user, args, { models }) => { return await models.Note.find({ favoritedBy: user._id }).sort({ _id: -1 }); }};
当初咱们须要更新src/resolvers/index.js
导入和导出这些新的解析器模块。总体而言,src/resolvers/index.js
文件当初应如下所示:
const Query = require('./query');const Mutation = require('./mutation');const Note = require('./note');const User = require('./user');const { GraphQLDateTime } = require('graphql-iso-date');module.exports = { Query, Mutation, Note, User, DateTime: GraphQLDateTime};
当初,如果咱们编写一个嵌套的GraphQL
查问或批改,咱们将收到咱们冀望的信息。你能够通过编写以下笔记查问来进行尝试:
query { note(id: "<YOUR_NOTE_ID_HERE>") { id content # the information about the author note author { username id } }}
该查问应应用作者的用户名和ID
正确解析。另一个理论的示例是返回无关“喜爱”笔记的用户的信息:
mutation { toggleFavorite(id: "<YOUR NOTE ID>") { favoriteCount favoritedBy { username } }}
应用嵌套的解析器,咱们能够编写准确的查问和批改,以准确返回所需的数据。
论断
祝贺你!在本章中,咱们的API
逐步成为一种用户能够真正与之交互的货色。该API
通过集成用户操作/增加新性能和嵌套解析器来展现GraphQL
的真正性能。咱们还遵循了一种将实在的代码增加到我的项目中的尝试模式:首先编写GraphQL
模式,而后编写数据库模块,最初编写解析器代码以查问或更新数据。通过将过程分为三个步骤,咱们能够向咱们的应用程序增加各种性能。在下一章中,咱们将介绍使API
产品准备就绪所需的最初步骤,包含分页和安全性。
如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。