用Hapi和TypeScript为Jamstack构建一个Rest API

    卡米洛·雷耶斯
    分享

    Jamstack有一种很好的方法可以将前端和后端分开,这样整个解决方案就不必在一个单一的巨石中发布了——而且所有这些都是在同一时间完成的。当Jamstack与REST API配对时,客户端和API可以发展独立.这意味着前端和后端不是紧密耦合的,改变其中一个并不一定意味着改变另一个。

    在本文中,我将从Jamstack的角度看一看REST API。我将展示如何在不破坏现有客户机的情况下改进API并遵循REST标准。我将选择Hapi作为构建API的工具,而Joi用于端点验证。数据库持久化层将通过Mongoose进入MongoDB来访问数据。测试驱动开发将帮助我迭代更改,并提供一种快速获得反馈的方法,同时减少认知负荷。最后,我们的目标是让您了解REST和Jamstack如何在软件模块之间提供高内聚和低耦合的解决方案。这种类型的体系结构最适合具有大量微服务的分布式系统,每个微服务都在自己的独立域中。我将假设具有NPM、ES6+的工作知识,并基本熟悉API端点。

    该API将与作者数据一起工作,包括姓名、电子邮件和可选的1:N (一对几通过文档嵌入)在喜欢的话题上的关系。我将编写GET、PUT(带upsert)和DELETE端点。为了测试API,任何支持fetch ()就行了,我来选Hoppscotch和旋度。

    我将把这篇文章的阅读流程作为一个教程,你可以从头到尾跟着我读。对于那些宁愿跳过代码的人来说,它是可以在GitHub上找到供您观赏。本教程假设Node的工作版本(最好是最新的LTS)和MongoDB已安装

    初始设置

    要从头开始项目,请创建一个文件夹和cd成:

    mkdirhapi-authors-rest-apicdhapi-authors-rest-api

    进入项目文件夹后,启动npm init按照提示操作。这将创建一个package.json在文件夹的根目录。

    每个Node项目都有依赖关系。我需要Hapi, Joi和Mongoose开始:

    npm我@hapi/hapi joi猫鼬——save-exact

    检查package.json确保所有依赖项和项目设置都到位。然后,在这个项目中添加一个入口点:

    “脚本”“开始”“节点index.js”

    带有版本的MVC文件夹结构

    对于这个REST API,我将使用带有控制器、路由和数据库模型的典型MVC文件夹结构。控制器将有一个类似的版本AuthorV1Controller允许API在模型发生突破性变化时进行演化。幸福就会有一个server.js而且index.js通过测试驱动开发使该项目可测试。的测验文件夹将包含单元测试。

    下面是文件夹的整体结构:

    ┳┣━┓配置┃┣━━dev.json┃┗━━index.js┣━┓控制器┃┗━━AuthorV1Controller.js┣━┓┃模型┣━━Author.js┃┗━━index.js┣━┓┃路线┣━━authors.js┃┗━━index.js┣━┓测试┃┗━━Author.js┣━━index.js┣━━包。Json -━━server.js

    现在,继续在每个文件夹中创建文件夹和各自的文件。

    mkdir配置控制器模型路由测验触摸配置/ dev.json配置/ index.js控制器/ AuthorV1Controller.js模型/Author.js model/index.js routes/authors.js routes/index.js test/Authors.js index.js server.js

    这是每个文件夹的目的:

    • 配置:配置信息,以插入到Mongoose连接和Hapi服务器。
    • 控制器:这些是处理请求/响应对象的Hapi处理程序。版本控制允许每个版本号有多个端点,也就是说,/ v1 /作者/ v2 /作者等。
    • 模型:连接MongoDB数据库,定义Mongoose模式。
    • 路线:为REST纯粹主义者定义了使用Joi验证的端点。
    • 测验:通过Hapi的实验室工具进行单元测试。(稍后再详细介绍。)

    在实际项目中,您可能会发现将常见业务逻辑抽象到一个单独的文件夹中是很有用的跑龙套.我建议创建一个AuthorUtil.js模块与纯功能代码,使此可跨端点重用和易于单元测试。因为这个解决方案没有任何复杂的业务逻辑,所以我选择跳过这个文件夹。

    添加更多文件夹的一个缺点是在进行更改时有更多的抽象层和更多的认知负荷。对于异常庞大的代码库,很容易迷失在混乱的误导层中。有时最好保持文件夹结构尽可能简单和平坦。

    打印稿

    为了改善开发人员的体验,我现在将添加TypeScript类型声明。因为Mongoose和Joi在运行时定义模型,所以在编译时添加类型检查器没有什么价值。在TypeScript中,可以将类型定义添加到普通JavaScript项目中,同时仍然可以在代码编辑器中获得类型检查器的好处。像WebStorm或VS Code这样的工具会选择类型定义,并允许程序员在代码中“点”。这种技术通常被称为智能感知,并且当IDE有可用的类型时启用它。这是一种定义编程接口的好方法,这样开发人员就可以在不查看文档的情况下点入对象。当开发人员点入错误的对象时,编辑器有时也会显示警告。

    这是智能感知在VS Code中的样子:

    VSCode智能感知

    在WebStorm中,这被称为代码完成,但本质上是一样的。您可以随意选择您喜欢的IDE来编写代码。我使用Vim和WebStorm,但你可以选择不同的。

    要在这个项目中启用TypeScript类型声明,启动NPM并保存这些开发者依赖项:

    npmI @types/hapi @types/mongoose—save-dev

    我建议将开发人员依赖关系与应用程序依赖关系分开。这样,组织中的其他开发人员就很清楚这些包的用途。当构建服务器下拉repo时,它还可以选择跳过项目在运行时不需要的包。

    所有开发人员的细节都到位之后,现在是时候开始编写代码了。打开Hapiserver.js归档并放置主服务器:

    常量配置需要”。/配置常量路线需要“/路线。”常量db需要”。/模型”常量哈皮神需要“@hapi /哈皮神”常量服务器哈皮神服务器港口配置APP_PORT宿主配置APP_HOST路线歌珥真正的服务器路线路线出口初始化异步= >等待服务器初始化等待db连接返回服务器出口开始异步= >等待服务器开始等待db连接控制台日志服务器运行在:$ {服务器信息uri返回服务器过程“unhandledRejection”犯错= >控制台错误犯错过程退出1

    我已经通过设置启用了CORS歌珥为true,所以这个REST API可以与Hoppscotch一起工作。

    为了简单起见,我在这个项目中不使用分号。跳过一个有点自由TypeScript构建在这个项目中然后输入额外的字符。这遵循了Hapi的原则,因为这是关于开发者的快乐。

    配置/ index.js,一定要导出dev.json信息:

    模块出口需要“/ dev。”

    要充实服务器的配置,可以添加这个dev.json

    “APP_PORT”3000“APP_HOST”“127.0.0.1”

    其他验证

    为了使REST端点遵循HTTP标准,我将添加Joi验证。这些验证有助于将API与客户机分离,因为它们强制执行资源完整性。对于Jamstack,这意味着客户端不再关心每个资源背后的实现细节。可以独立地处理每个端点,因为验证将确保对资源的有效请求。坚持严格的HTTP标准使客户端基于位于HTTP边界后面的目标资源进行演进,从而强制解耦。实际上,我们的目标是使用版本控制和验证来在Jamstack中保持一个清晰的边界。

    使用REST,主要目标是维护幂等性使用GET、PUT和DELETE方法。这些是安全的请求方法,因为对同一资源的后续请求没有任何副作用。即使客户端未能建立连接,也会重复相同的预期效果。

    我将选择跳过POST和PATCH,因为这些都不是安全的方法。这是为了简洁和幂等性,而不是因为这些方法以任何方式紧耦合客户端。同样严格的HTTP标准也适用于这些方法,除了它们不能保证幂等性。

    路线/ authors.js,添加以下Joi验证:

    常量对未来需要“未来”常量authorV1Params对未来对象id对未来字符串要求常量authorV1Schema对未来对象的名字对未来字符串要求电子邮件对未来字符串电子邮件要求主题对未来数组项目对未来字符串/ /可选createdAt对未来日期要求

    注意,对版本化模型的任何更改都可能需要一个新版本,比如v2.这保证了现有客户端的向后兼容性,并允许API独立地发展。当缺少字段时,必填字段将以400(坏请求)响应失败请求。

    设置好参数和模式验证后,向该资源添加实际路由:

    / /线路/ authors.js常量v1Endpoint需要“. . /控制器/ AuthorV1Controller”模块出口方法“得到”路径“/ v1 /作者/ {id}”处理程序v1Endpoint细节选项验证参数个数authorV1Params响应模式authorV1Schema方法“把”路径“/ v1 /作者/ {id}”处理程序v1Endpoint插入选项验证参数个数authorV1Params有效载荷authorV1Schema响应模式authorV1Schema方法“删除”路径“/ v1 /作者/ {id}”处理程序v1Endpoint删除选项验证参数个数authorV1Params

    使这些路线可用server.js把这个加进去路线/ index.js

    模块出口...需要”。/作者的

    Joi验证在选项路由数组的字段。属性对应的字符串ID参数ObjectId在MongoDB。这id是版本化路由的一部分,因为它是客户机需要使用的目标资源。对于PUT,有一个与GET响应匹配的有效负载验证。这是为了坚持REST标准的地方PUT响应必须匹配后续的GET

    标准是这么说的:

    对给定表示的成功PUT将表明对同一目标资源的后续GET将导致在200 (OK)响应中发送等效表示。

    这使得PUT不适合支持部分更新,因为后续的GET将不匹配PUT。对于Jamstack来说,遵循HTTP标准以确保客户端和解耦的可预测性是很重要的。

    AuthorV1Controller中的方法处理程序处理请求v1Endpoint.每个版本都有一个控制器是个好主意,因为这是将响应发送回客户端的方法。这使得它更容易通过一个新的版本控制器来发展API,而不会破坏现有的客户端。

    作者的数据库集合

    Node的Mongoose对象建模首先需要安装一个MongoDB数据库。我建议在您的本地开发设备上设置一个玩MongoDB.最低安装只需要两个可执行文件,你可以在大约50 MB的时间内启动并运行服务器。这是MongoDB的真正力量,因为一个完整的数据库可以在非常便宜的硬件上运行,比如树莓派,并且这可以水平扩展到尽可能多的盒子。该数据库还支持混合模型,其中服务器可以在云和on-prem上运行。所以,不要找借口!

    模型文件夹,打开index.js使用实例建立数据库连接。

    常量配置需要“. . /配置”常量猫鼬需要“猫鼬”模块出口连接异步函数等待猫鼬连接配置DB_HOST+' / '+配置DB_NAME配置DB_OPTS连接猫鼬连接作者需要”。/作者

    注意作者集合定义在Author.js在同一个文件夹中:

    常量猫鼬需要“猫鼬”常量authorSchema猫鼬模式的名字字符串电子邮件字符串主题字符串createdAt日期如果authorSchema选项toObjectauthorSchema选项toObjectauthorSchema选项toObject变换函数医生受潮湿腐烂删除受潮湿腐烂_id删除受潮湿腐烂__v如果受潮湿腐烂主题& &受潮湿腐烂主题长度= = =0删除受潮湿腐烂主题返回受潮湿腐烂模块出口猫鼬模型“作者”authorSchema

    请记住,Mongoose模式并不反映与Joi验证相同的需求。这增加了数据的灵活性,以支持多个版本,以防有人需要跨多个端点的向后兼容性。

    toObject变换清除JSON输出,这样Joi验证器就不会抛出异常。如果有任何额外的字段,比如_id,在Mongoose文档中,服务器发送一个500(内部服务器错误)响应。可选字段主题当它是一个空数组时,会被破坏,因为GET必须匹配PUT响应。

    最后,设置数据库配置配置/ dev.json

    “APP_PORT”3000“APP_HOST”“127.0.0.1”“DB_HOST”“mongodb: / / 127.0.0.1:27017”“DB_NAME”“hapiAuthor”“DB_OPTS”“useNewUrlParser”真正的“useUnifiedTopology”真正的“poolSize”1

    行为驱动开发

    在为控制器中的每个方法充实端点之前,我喜欢从编写单元测试开始。这有助于我对手头的问题进行概念化,从而获得最佳的代码。我将使用红色/绿色,但跳过重构,并将其作为练习留给您,以免赘述这一点。

    我将选择Hapi的实验室实用程序和他们的BDD断言库来测试我写的代码:

    npmI @hapi/lab @hapi/code—save-dev

    测试/ Author.js将这个基本的脚手架添加到测试代码中。我将选择行为驱动开发(BDD)风格,使其更加流畅:

    常量实验室需要“@hapi /实验室”常量预计需要“@hapi /代码”常量之前描述出口实验室实验室脚本常量初始化需要“. . /服务器”常量连接需要“. . /模式”常量id“5 ff8ea833609e90fc87fee52”常量有效载荷的名字“C R”电子邮件“xyz@abc.net”createdAt2021 - 01 - 08 - t06:00:00.000z描述' / v1 /作者的= >服务器之前异步= >服务器等待初始化异步= >等待服务器停止等待连接关闭

    当您构建更多的模型和端点时,我建议在每个测试文件中重复相同的脚手架代码。单元测试不是DRY(“不要重复你自己”),启动/停止服务器和数据库连接是完全没问题的。MongoDB连接和Hapi服务器可以处理这个问题,同时保持测试流畅。

    除了一个小问题外,测试几乎已经准备好运行了AuthorV1Controller1,因为它是空的。打开控制器/ AuthorV1Controller.js再加上这个:

    出口细节= >出口插入= >出口删除= >

    测试通过npm t在终点站。一定要把它放进去package.json

    “脚本”“测试”“实验室”

    继续,启动单元测试。应该还没有什么失败。要使单元测试失败,请将此添加到其中描述()

    “PUT响应201”异步= >常量statusCode等待服务器注入方法“把”url/ v1 /作者/$ {id有效载荷...有效载荷预计statusCode平等的201“PUT响应200”异步= >常量statusCode等待服务器注入方法“把”url/ v1 /作者/$ {id有效载荷...有效载荷主题JavaScript的MongoDB的预计statusCode平等的200'GET响应200'异步= >常量statusCode等待服务器注入方法“得到”url/ v1 /作者/$ {id预计statusCode平等的200'DELETE响应204'异步= >常量statusCode等待服务器注入方法“删除”url/ v1 /作者/$ {id预计statusCode平等的204

    要开始通过单元测试,请把这个放在里面控制器/ AuthorV1Controller.js

    常量db需要“. . /模式”出口细节异步请求h= >常量作者等待db作者findById请求参数个数id执行请求日志“实现”GET 200 /v1/authors$ {作者返回h响应作者toObject出口插入异步请求h= >常量作者等待db作者findById请求参数个数id执行如果作者常量newAuthordb作者请求有效载荷newAuthor_id请求参数个数id等待newAuthor保存请求日志“实现”PUT 201 /v1/authors$ {newAuthor返回h响应newAuthortoObject创建/ v1 /作者/$ {请求参数个数id作者的名字请求有效载荷的名字作者电子邮件请求有效载荷电子邮件作者主题请求有效载荷主题请求日志“实现”PUT 200 /v1/authors$ {作者等待作者保存返回h响应作者toObject出口删除异步请求h= >等待db作者findByIdAndDelete请求参数个数id请求日志“实现”删除204 /v1/authors$ {请求参数个数id返回h响应代码204

    这里有几件事需要注意。的exec ()方法是物化查询并返回Mongoose文档的方法。因为这个文档有Hapi服务器不关心的额外字段,所以应用toObject在调用之前反应().API的默认状态代码是200,但是可以通过代码()创建()

    在红色/绿色/重构测试驱动开发中,我只编写了通过测试所需的最少的代码。我将把编写更多的单元测试和用例留给您。例如,当目标资源没有作者时,GET和DELETE应该返回404 (Not Found)。

    Hapi支持其他特性,比如在请求对象。作为默认值,实现标签在服务器运行时将调试日志发送到控制台,这也适用于单元测试。这是一种很好的干净的方式,可以查看请求通过请求管道时发生了什么。

    测试

    最后,在启动主服务器之前,把这个放进去index.js

    常量开始需要“。/服务器”开始

    一个npm开始应该让你在Hapi中得到一个运行和工作的REST API。现在我将使用Hoppscotch向所有端点发出请求。你所要做的就是点击下面的链接来测试你的API。请务必从上到下点击链接:

    或者,在cURL中也可以做同样的事情:

    旋度-i -X PUT -H“application / json内容类型:- d“{\”的名字\”\”C R\”\”电子邮件\”\”xyz@abc.net\”\”createdAt\”\”2021 - 01 - 08 - t06:00:00.000z\”}”http://localhost:3000/v1/authors/5ff8ea833609e90fc87fee52201创建“名称”“C R”“电子邮件”“xyz@abc.net”“createdAt”“2021 - 01 - 08 - t06:00:00.000z”旋度-i -X PUT -H“application / json内容类型:- d“{\”的名字\”\”C R\”\”电子邮件\”\”xyz@abc.net\”\”createdAt\”\”2021 - 01 - 08 - t06:00:00.000z\”\”主题\”:【\”JavaScript\”\”MongoDB\”]}”http://localhost:3000/v1/authors/5ff8ea833609e90fc87fee52200好吧“主题”“JavaScript”“MongoDB”“名称”“C R”“电子邮件”“xyz@abc.net”“createdAt”“2021 - 01 - 08 - t06:00:00.000z”旋度-我- h“application / json内容类型:http://localhost:3000/v1/authors/5ff8ea833609e90fc87fee52200好吧“主题”“JavaScript”“MongoDB”“名称”“C R”“电子邮件”“xyz@abc.net”“createdAt”“2021 - 01 - 08 - t06:00:00.000z”旋度-i -X DELETE -H“application / json内容类型:http://localhost:3000/v1/authors/5ff8ea833609e90fc87fee52204没有内容

    在Jamstack中,JavaScript客户端可以通过fetch ().REST API的好处在于它根本不需要是浏览器,因为任何支持HTTP的客户端都可以。这对于多个客户端可以通过HTTP调用API的分布式系统来说是完美的。API可以保持独立,具有自己的部署计划,并允许自由发展。

    结论

    JamStack有一种通过版本控制的端点和模型验证来解耦软件模块的好方法。Hapi服务器支持这一点和其他细节,如类型声明,使您的工作更愉快。

    Baidu