翻译 |《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 context
newNote: 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
产品准备就绪所需的最初步骤,包含分页和安全性。
如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。