共计 5392 个字符,预计需要花费 14 分钟才能阅读完成。
翻译 |《JavaScript Everywhere》第 9 章 详细信息(^_^)
写在最后面
大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。
为了进步大家的浏览体验,对语句的构造和内容略有调整。如果发现本文中有存在瑕疵的中央,或者你有任何意见或者倡议,能够在评论区留言,或者加我的微信:code\_maomao,欢送互相沟通交流学习。
(σ゚∀゚)σ..:\*☆哎哟不错哦
第 9 章 详细信息
当当初普遍存在的空气清新剂 Febreze
首次公布时,它就像是一个哑巴。
原始的广告只显示人们应用该产品能够去除特定的难闻气息,例如香烟烟雾,导致销售不佳。面对令人悲观的后果,营销团队将重点转移到应用 Febreze
作为完满的细节。当初,这些广告描述了有人清扫房间,松软枕头并以 Febreze
兴奋地实现陈腐房间的过程。产品的这种从新设计导致销量猛增。
这是细节很重要的一个很好的例子。
当初咱们有一个能够失常应用的API
,然而它短少使咱们可能投入生产的画龙点睛的性能。
在本章中,咱们将实现一些 Web
和GraphQL
应用程序安全性以及用户体验的最佳实际。
这些细节远远超出了空气清新剂的喷洒范畴,对于咱们应用程序的安全性,安全性和可用性至关重要。
Web 应用程序和 Express.js 最佳实际
Express.js
是反对咱们 API
的底层 Web
应用程序框架。咱们能够对 Express.js
代码进行一些小调整,为咱们的应用程序提供松软的根底。
Express Helmet
the ExpressHelmet
中间件是小型安全性中间件性能的汇合。这些将调整咱们应用程序的 HTTP
头,以进步安全性。只管其中许多基于浏览器的应用程序,然而启用 Helmet
是爱护咱们的应用程序不受常见 Web
破绽影响的简略步骤。
要启用 Helmet
,咱们须要在应用程序中应用中间件,并批示Express
在尽早咱们的中间件堆栈中应用它。在./src/index.js
文件中,增加以下内容:
// first require the package at the top of the file
const helmet = require('helmet')
// add the middleware at the top of the stack, after const app = express()
app.use(helmet());
通过增加 Helmet
中间件,咱们迅速为咱们的应用程序启用了常见的 Web
平安最佳实际。
跨域资源共享
跨域资源共享(CORS
)是咱们容许从另一个域申请资源的办法。因为咱们的 API
和UI
代码将别离存在,因而咱们心愿启用其余起源的应用。如果你有趣味理解 CORS
的前因后果,我强烈建议你 Mozilla CORS
指南。
要启用 CORS
,咱们将在.src/index.js
文件中应用 Express.jsCORS
中间件软件包:
// first require the package at the top of the file
const cors = require('cors');
// add the middleware after app.use(helmet());
app.use(cors());
通过以这种形式增加中间件,咱们启用了来自所有域的跨域申请。目前,这对咱们来说很好,因为咱们处于开发模式,并且很可能会应用托管服务提供商生成的域,然而通过应用中间件,咱们也能够将申请限度为特定起源。
Pagination 分页
以后,咱们的笔记查问和用户查问都返回了数据库中全副列表的笔记和用户。
这对于本地开发而言很好用,然而随着咱们应用程序的增长,变得难以为继,因为查问可能返回多个(或数千个)笔记的查问十分低廉,并且会升高数据库、服务器和网络的速度。相同,咱们能够对这些查问进行分页,仅返回肯定数量的后果。咱们能够实现两种常见的分页类型。
第一种偏移分页,由客户端传递偏移号并返回无限数量的数据来工作。
例如,如果每页数据限度为 10
条记录,而咱们想申请第三页数据,则能够传递 20
的偏移量。尽管从概念上讲这是最间接的办法,但它可能会遇到扩大和性能问题。
第二种分页是基于游标的分页,其中将基于工夫的游标或惟一标识符作为终点传递。而后,咱们要求遵循此记录的特定数量的数据。这种办法使咱们能够最大水平地管制分页。另外,因为 Mongo
的对象 ID
是有序的(它们以 4
字节的工夫值结尾),因而咱们能够轻松地将其用作光标。要理解无关 Mongo
对象 ID
的更多信息,倡议浏览相应的 MongoDB
文档。
如果听不懂这个概念,那没关系。让咱们逐渐实现将笔记的分页作为一个 GraphQL
查问。首先,让咱们定义将要创立的内容,而后是更新模式,最初是解析器代码。对于咱们的需要,咱们要查问咱们的 API
,同时能够抉择将游标作为参数传递。而后,API
应该返回无限数量的数据,示意数据集中最初一个的光标点以及如果要查问的另一页数据的布尔值。
通过此形容,咱们能够更新 src/schema.js
文件来定义此新查问。首先,咱们须要在文件中增加一个 NoteFeed
类型:
type NoteFeed {notes: [Note]!
cursor: String!
hasNextPage: Boolean!
}
接下来,咱们将增加咱们的 noteFeed
查问:
type Query {
# add noteFeed to our existing queries
noteFeed(cursor: String): NoteFeed
}
更新构造后,咱们能够编写咱们查问的解析器代码。在./src/resolvers/query.js
中,将以下内容增加到导出的对象中:
noteFeed: async (parent, { cursor}, {models}) => {
// hardcode the limit to 10 items
const limit = 10;
// set the default hasNextPage value to false
let hasNextPage = false;
// if no cursor is passed the default query will be empty
// this will pull the newest notes from the db
let cursorQuery = {};
// if there is a cursor
// our query will look for notes with an ObjectId less than that of the cursor
if (cursor) {cursorQuery = { _id: { $lt: cursor} };
}
// find the limit + 1 of notes in our db, sorted newest to oldest
let notes = await models.Note.find(cursorQuery)
.sort({_id: -1})
.limit(limit + 1);
// if the number of notes we find exceeds our limit
// set hasNextPage to true and trim the notes to the limit
if (notes.length > limit) {
hasNextPage = true;
notes = notes.slice(0, -1);
}
// the new cursor will be the Mongo object ID of the last item in the feed array
const newCursor = notes[notes.length - 1]._id;
return {
notes,
cursor: newCursor,
hasNextPage
};
}
应用此解析器后,咱们能够查问咱们的 noteFeed
,最多返回10
个后果。在 GraphQL Playground
中,咱们能够编写以下查问来接管笔记,它们的对象ID
,它们的“创立于”工夫戳,光标和下一页布尔值的列表:
query {
noteFeed {
notes {
id
createdAt
}
cursor
hasNextPage
}
}
因为咱们的数据库中有 10
个以上的笔记,因而它返回一个游标以及 hasNextPage
值true
。应用该光标,咱们能够查问提要的第二页:
query {noteFeed(cursor: "<YOUR OBJECT ID>") {
notes {
id
createdAt
}
cursor
hasNextPage
}
}
咱们能够持续对 hasNextPage
值为 true
的每个游标执行此操作。有了这个实现,咱们就创立了一个分页的笔记块。这不仅使咱们的 UI
能够申请特定的数据块,还能够加重服务器和数据库的累赘。
数据限度
除了建设分页之外,咱们还要限度能够通过咱们的 API
申请的数据量。这样能够避免查问使咱们的服务器或数据库超载。
此过程中的第一步很简略,就是限度查问能够返回的数据量。咱们的两个查问,user
和 notes
,从数据库返回所有匹配的数据。咱们能够通过设置一个咱们的数据库查问的limit
() 办法。例如,在咱们的.src/resolvers/query.js
文件中,咱们能够按以下形式更新笔记查问:
notes: async (parent, args, { models}) => {return await models.Note.find().limit(100);
}
尽管限度数据是一个很好的开始,但目前咱们的查问能够有限深度地编写。这意味着能够编写单个查问来检索笔记列表、每个笔记的作者信息、每个作者的收藏夹列表、每个收藏夹的作者信息,等等。一个查问中有很多数据,咱们持续编写!为了避免这些类型的适度查问,咱们能够依据 API
限度查问的深度。
此外,咱们可能有一些简单的查问,这些查问没有过多嵌套,但依然须要大量计算能力返回数据。咱们能够通过限度查问的复杂性来避免这类申请。
咱们能够通过应用./src/index.js
文件中的 graphql-depth-limit
和graphql-validation-complexity
包来实现这些限度:
// import the modules at the top of the file
const depthLimit = require('graphql-depth-limit');
const {createComplexityLimitRule} = require('graphql-validation-complexity');
// update our ApolloServer code to include validationRules
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5), createComplexityLimitRule(1000)],
context: async ({req}) => {
// get the user token from the headers
const token = req.headers.authorization;
// try to retrieve a user with the token
const user = await getUser(token);
// add the db models and the user to the context
return {models, user};
}
});
通过增加这些软件包,咱们为 API
增加了额定的查问爱护。无关爱护 GraphQL API
免受歹意查问的更多信息,请查看 Spectrum
首席技术官 Max Stoiber
的精彩文章。
其余注意事项
构建咱们的 API
后,你应该对 GraphQL
开发的基础知识有扎实的理解。如果你想深刻理解这些主题,那么接下来能够去测试,GraphQL
订阅和 Apollo Engine
就是一些不错的抉择。
测验
好吧,我抵赖:我没有为本书写测试而感到内疚。测试咱们的代码很重要,因为它使咱们可能轻松进行更改并改善与其余开发人员的合作。GraphQL
设置的一大长处是,解析器只是简略的函数,须要一些参数并返回数据。这使咱们的 GraphQL
逻辑易于测试。
订阅内容
订阅是 GraphQL
弱小的性能,它提供了一种间接的形式来将公布 - 订阅模式集成到咱们的应用程序中。这意味着在服务器上公布数据时,UI
能够订阅告诉或更新。这使 GraphQL
服务器成为解决实时数据的应用程序的现实解决方案。无关 GraphQL
订阅的更多信息,请查看 Apollo
服务器文档。
Apollo GraphQL
平台
在开发 API
的整个过程中,咱们始终在应用 Apollo GraphQL
库。在当前的章节中,咱们还将应用 Apollo
客户端库与 API
进行接口。我之所以抉择这些库是因为它们是行业标准,并且为应用 GraphQL
提供了杰出的开发人员教训。如果你将应用程序投入生产,则保护这些库的公司 Apollo
还将提供一个平台,该平台提供对 GraphQL API
的监督和工具。你能够在以下处理解更多信息Apollo
(Apollo
)的网站。
论断
在本章中,咱们为咱们的应用程序增加了一些收尾工作。尽管咱们能够实现许多其余的抉择,但在这一点上,咱们曾经开发了一个牢靠的MVP
(最小可行产品)。在这种状态下,咱们筹备好启动咱们的API
!
在下一章中,咱们将把咱们的 API
部署到一个公共 web
服务器上。
如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。