翻译|《JavaScript Everywhere》第9章详细资料(^_^)写在置顶前端开发工程师。翻译一本英文技术书籍。为了提高大家的阅读体验,对句子的结构和内容进行了微调。如果大家发现本文有什么不妥之处,或者有什么意见和建议,可以在评论区留言,或者加我的微信:code\_maomao,欢迎交流,互相学习。(σ???)σ..:\*☆哎哟还不错第9话详情当现在无处不在的空气清新剂纺必适刚上市的时候,它就像一个哑弹。最初的广告只展示了人们使用该产品去除特定的难闻气味,例如香烟烟雾,导致销量不佳。面对令人失望的结果,营销团队将注意力转移到使用Febreze作为完美细节。这些广告现在描绘的是有人打扫房间、弄松枕头并用纺必适(Febreze)兴奋地整理房间。产品的重新设计导致销量激增。这是细节重要性的一个很好的例子。现在我们有了一个可用的API,但它缺少使我们投入生产的收尾工作。在本章中,我们将实施一些Web和GraphQL应用程序安全和用户体验最佳实践。这些细节远远超出了空气清新剂的喷雾范围,对我们应用程序的安全性、保障性和可用性至关重要。Web应用程序和Express.js的最佳实践Express.js是为我们的API提供支持的底层Web应用程序框架。我们可以对Express.js代码做一些小的调整,为我们的应用程序提供坚实的基础。ExpressHelmetExpressHelmet中间件是小型安全中间件功能的集合。这些将调整我们应用程序的HTTP标头以提高安全性。虽然其中许多是基于浏览器的应用程序,但启用Helmet是保护我们的应用程序免受常见Web漏洞攻击的简单步骤。要启用Helmet,我们需要在我们的应用程序中使用中间件,并指示Express在我们的中间件堆栈中尽早使用它。在./src/index.js文件中,添加如下内容://首先require文件顶部的包consthelmet=require('helmet')//添加中间件在栈顶,constapp之后=express()app.use(头盔());通过添加Helmet中间件,我们快速为我们的应用程序启用了常见的Web安全最佳实践。跨源资源共享跨源资源共享(CORS)是我们允许从另一个域请求资源的方法。由于我们的API和UI代码将分开存在,因此我们希望启用来自其他来源的使用。如果您有兴趣了解CORS的来龙去脉,我强烈推荐MozillaCORS指南。要启用CORS,我们将使用.src/index.js文件中的Express.jsCORS中间件包://首先需要文件顶部的包constcors=require('cors');//在app.use(helmet());app.use(cors());之后添加中间件通过这种方式添加中间件,我们可以实现来自所有域的跨域请求。目前这对我们来说很好用,因为我们处于开发模式,很可能会使用我们的托管服务提供商生成的域,但通过使用中间件,我们还可以将请求限制到特定来源。分页目前,我们的笔记查询和用户查询都返回数据库中笔记和用户的完整列表。这对于本地开发来说效果很好,但随着我们的应用程序的增长变得不可持续,因为可以返回许多(或数千)条笔记的查询非常昂贵,并且会减慢数据库、服务器和网络速度。相反,我们可以对这些查询进行分页以仅返回一定数量的结果。我们可以实现两种常见的分页类型。第一种类型的偏移分页通过客户端传递偏移量并返回有限数量的数据来工作。例如,如果每页数据限制为10条记录并且我们想要请求第三页数据,我们可以传递20的偏移量。虽然从概念上讲这是最直接的方法,但它可能会遇到缩放和性能问题。第二种类型的分页是基于游标的分页,其中传递基于时间的游标或唯一标识符作为起点。然后,我们要求在该记录之后提供一定数量的数据。这种方法使我们能够最大程度地控制分页。此外,由于Mongo的对象ID是有序的(它们以4字节时间值开头),我们可以轻松地将其用作游标。要了解有关Mongo对象ID的更多信息,建议阅读相应的MongoDB文档。如果你不理解这个概念,没关系。让我们将笔记的分页实现为GraphQL查询。首先,让我们定义将要创建的内容,然后是更新模式,最后是解析器代码。出于我们的需要,我们希望查询我们的API,同时可选择将游标作为参数传递。然后,API应该返回有限数量的数据,一个布尔值,表示数据集中最后一个的光标点,以及是否有另一页数据要查询。有了这个描述,我们可以更新src/schema.js文件来定义这个新查询。首先,我们需要在文件中添加一个NoteFeed类型:typeNoteFeed{notes:[Note]!光标:字符串!hasNextPage:Boolean!}接下来,我们将添加noteFeed查询:typeQuery{#addnoteFeedtoourexistingqueriesnoteFeed(cursor:String):NoteFeed}更新结构后,我们可以为查询编写解析器代码。在./src/resolvers/query.js中,将以下内容添加到导出的对象中:noteFeed:async(parent,{cursor},{models})=>{//hardcodethelimitto10itemsconstlimit=10;//将默认的hasNextPage值设置为falselethasNextPage=false;//如果没有传递游标,则默认查询将为空//这将从数据库中提取最新的注释letcursorQuery={};//如果有游标//我们的查询将查找ObjectId小于游标的注释if(cursor){cursorQuery={_id:{$lt:cursor}};}//在我们的数据库中找到limit+1个笔记,从最新到最旧排序letnotes=awaitmodels.Note.find(cursorQuery).sort({_id:-1}).limit(limit+1);//如果我们找到的笔记数量超过限制//将hasNextPage设置为true并将笔记修剪到限制if(notes.length>limit){hasNextPage=true;notes=notes.slice(0,-1);}//新光标将是feed数组中最后一项的Mongo对象IDconstnewCursor=notes[notes.length-1]._id;return{notes,cursor:newCursor,hasNextPage};}在使用这个解析器之后,我们可以查询我们的noteFeed并返回最多10个结果。在GraphQLPlayground中,我们可以编写以下查询来接收笔记列表、它们的对象ID、它们的“创建时间”时间戳、游标和下一页布尔值:query{noteFeed{notes{idcreatedAt}cursorhasNextPage}}由于我们的数据库中有超过10条笔记,所以它返回一个hasNextPage值为true的游标。使用该游标,我们可以查询提要的第二页:query{noteFeed(cursor:"
