翻译 | 《JavaScript Everywhere》第9章 详细信息(^_^)

写在最后面

大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。

为了进步大家的浏览体验,对语句的构造和内容略有调整。如果发现本文中有存在瑕疵的中央,或者你有任何意见或者倡议,能够在评论区留言,或者加我的微信:code\_maomao,欢送互相沟通交流学习。

(゚∀゚)..:\*☆哎哟不错哦

第9章 详细信息

当当初普遍存在的空气清新剂Febreze首次公布时,它就像是一个哑巴。

原始的广告只显示人们应用该产品能够去除特定的难闻气息,例如香烟烟雾,导致销售不佳。面对令人悲观的后果,营销团队将重点转移到应用Febreze作为完满的细节。当初,这些广告描述了有人清扫房间,松软枕头并以Febreze兴奋地实现陈腐房间的过程。产品的这种从新设计导致销量猛增。

这是细节很重要的一个很好的例子。

当初咱们有一个能够失常应用的API,然而它短少使咱们可能投入生产的画龙点睛的性能。

在本章中,咱们将实现一些WebGraphQL应用程序安全性以及用户体验的最佳实际。

这些细节远远超出了空气清新剂的喷洒范畴,对于咱们应用程序的安全性,安全性和可用性至关重要。

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 fileconst helmet = require('helmet')// add the middleware at the top of the stack, after const app = express()app.use(helmet()); 

通过增加Helmet中间件,咱们迅速为咱们的应用程序启用了常见的Web平安最佳实际。

跨域资源共享

跨域资源共享(CORS)是咱们容许从另一个域申请资源的办法。因为咱们的APIUI代码将别离存在,因而咱们心愿启用其余起源的应用。如果你有趣味理解CORS的前因后果,我强烈建议你Mozilla CORS指南。

要启用CORS,咱们将在.src/index.js文件中应用Express.jsCORS中间件软件包:

// first require the package at the top of the fileconst 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个以上的笔记,因而它返回一个游标以及hasNextPagetrue。应用该光标,咱们能够查问提要的第二页:

query {  noteFeed(cursor: "<YOUR OBJECT ID>") {   notes {    id    createdAt   }   cursor   hasNextPage  }} 

咱们能够持续对hasNextPage值为true的每个游标执行此操作。有了这个实现,咱们就创立了一个分页的笔记块。这不仅使咱们的UI能够申请特定的数据块,还能够加重服务器和数据库的累赘。

数据限度

除了建设分页之外,咱们还要限度能够通过咱们的API申请的数据量。这样能够避免查问使咱们的服务器或数据库超载。

此过程中的第一步很简略,就是限度查问能够返回的数据量。咱们的两个查问,usernotes,从数据库返回所有匹配的数据。咱们能够通过设置一个咱们的数据库查问的limit()办法。例如,在咱们的.src/resolvers/query.js文件中,咱们能够按以下形式更新笔记查问:

notes: async (parent, args, { models }) => {  return await models.Note.find().limit(100);}

尽管限度数据是一个很好的开始,但目前咱们的查问能够有限深度地编写。这意味着能够编写单个查问来检索笔记列表、每个笔记的作者信息、每个作者的收藏夹列表、每个收藏夹的作者信息,等等。一个查问中有很多数据,咱们持续编写!为了避免这些类型的适度查问,咱们能够依据API限度查问的深度。

此外,咱们可能有一些简单的查问,这些查问没有过多嵌套,但依然须要大量计算能力返回数据。咱们能够通过限度查问的复杂性来避免这类申请。

咱们能够通过应用./src/index.js文件中的graphql-depth-limitgraphql-validation-complexity包来实现这些限度:

// import the modules at the top of the fileconst depthLimit = require('graphql-depth-limit');const { createComplexityLimitRule } = require('graphql-validation-complexity');// update our ApolloServer code to include validationRulesconst 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的监督和工具。你能够在以下处理解更多信息ApolloApollo)的网站。

论断

在本章中,咱们为咱们的应用程序增加了一些收尾工作。尽管咱们能够实现许多其余的抉择,但在这一点上,咱们曾经开发了一个牢靠的MVP(最小可行产品)。在这种状态下,咱们筹备好启动咱们的API

在下一章中,咱们将把咱们的API部署到一个公共web服务器上。

如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。