构建|构建 Node.js REST API 的十条实践

2017 年 5 月 15 日起, Scott 会陆续翻译一些不错或者有趣的 Node.js 文章,干货湿货一起撸,帮助 Node.js 爱好者更好的了解 Node.js 这个技术所带来的生产力和职业竞争力。
另外 Scott 也录制了 一些 Node.js 的实战学习视频,有免费的有收费的,大家可以自取所需,下面进入正题:
本文来自 risingstack 官方博客,作者 Gergely Nemeth
Node.js and microservices 开发者,也是 @Oneshotbudapest @nodebp @jsconfbp 的组织者,文章发表于 2017 3 月份 10 Best Practices for Writing Node.js REST APIs
译文如下:
在这篇文章中,我们介绍了编写 Node.js REST API 的最佳实践实践,包括路由命名,身份验证,黑盒测试以及为这些资源使用适当的缓存策略等。
【构建|构建 Node.js REST API 的十条实践】Node.js 最受欢迎的一个用法是使用它构建 RESTful API。 尽管如此,在我们帮助客户在应用程序中使用 [Trace](https://trace.risingstack.com/),经常会发现开发人员在 REST API 方面有很多问题。
我希望我们在 [RisingStack](https://risingstack.com/)上使用的这些方法可以帮助到大家:
1 - 使用 HTTP 方法和 API 路由
想象一下,您正在构建一个用于创建,更新,检索或删除用户的 Node.js RESTful API。 对于这些操作,HTTP 本身就有足够的方法集:POST,PUT,GET,PATCH 或 DELETE。
最好是你的 API 路由始终使用这些方法名字来作为资源标识。提到用户资源,路由可能像下面这样:
  • POST /user or PUT /user:/id 用来建立一个新用户
  • GET /user 用来获取用户列表
  • GET /user/:id 用来拿到用户
  • PATCH /user/:id 修改现有的用户记录
  • DELETE /user/:id 用来移除用户.
"API routes should always use nouns as resource identifiers!" via @RisingStack
2 - 正确使用 HTTP 状态码
如果服务请求出现问题,你必须在响应中设置正确的状态码:
  • 2xx, 一切都 OK
  • 3xx, 有些资源被移动了
  • 4xx, 客户端请求不满足条件不合法 (比如请求了一个不存在的资源),
  • 5xx, API 服务器端出错 (比如发生了一个异常).
如果你用 Express ,把状态代码设置为 res.status(500).send({error: 'Internal server error happened'}). 与 Restify: res.status(201) 相似.
想看完整列表的话,请查看HTTP 状态代码列表
3 - 使用 HTTP 头来发送元数据
当附加一些你想要发送的元数据时,要使用 HTTP 头。 可以像下面这样:
  • pagination
  • rate limiting
  • or authentication
在这里可以看到标准化的 HTTP 头列表.
如果你需要在头文件中设置任何自定义元数据的话,最好用 X 做前缀。
例如,如果您使用的是 CSRF 令牌,那么将它们命名为 X-Csrf-Token 令牌是一种常见的(但非标准的)方法。
然而,[RFC 6648](https://tools.ietf.org/html/rfc6648)已被弃用。 新的 API 应该尽量不要使用可能与其他应用程序冲突的标头名称。 例如,OpenStack 用 OpenStack 来作为头数据前缀:
OpenStack-Identity-Account-ID OpenStack-Networking-Host-Name OpenStack-Object-Storage-Policy

请注意标准化的 HTTP 是不定义头大小的;不过,Node.js (在撰写本文时)对头体积有 80KB 大小的限制。
*" HTTP 标头(包括状态行)的总大小不要超过 HTTP_MAX_HEADER_SIZE
. 这里做此检查的作用,就是用来保护服务器在受到 DoS 攻击时候占用过多网络资源而垮掉。来自
Node.js HTTP parser
4 - 为你的 Node.js REST API 选择合适的框架
选择一个合适的框架非常重要。
Express, Koa or Hapi Express, Koa 和 Hapi 可用于创建浏览器应用程序,比如它们支持模板和渲染。 如果您的应用程序需要提供面向用户的服务,那么就尽管去使用这些框架吧。
Restify 另一方面,Restify (http://restify.com/) 专注于帮你构建 REST 服务。它就是为了让你构建具有可维护性和可观察性的 “严格的” 的API 服务。Restify 还提供了自动的DTrace(http://dtrace.org/blogs/about/) 支持所有的处理程序。
Restify 也被用在一些主要的产品中,比如 npm 或 Netflix。
"Restify exists to let you build "strict" API services that are maintainable & observable." via @RisingStack
5 - 黑盒测试你的 Node.js REST APIs
测试 REST API 的最佳方式之一就是将它看成黑盒。黑盒测试是一种无需了解其内部结构或工作原理就可以测试应用程序功能的方法。
因此,所有依赖的模块都没有被模拟,也没有被去除,但是系统依然是作为一个整体进行测试的。
其中一个可以帮助您使用黑盒测试 Node.js REST API 的模块是 supertest.
一个简单的通过 mocha 来检查用户是否被返回的代码可以这样写:
const request = require('supertest')describe('GET /user/:id', function() { it('returns a user', function() { // newer mocha versions accepts promises as well return request(app) .get('/user') .set('Accept', 'application/json') .expect(200, { id: '1', name: 'John Math' }, done) }) })

您可能会问:数据是如何被生成后塞进去到数据的,从而来提供 REST API 的数据
一般来说,尽可能少地假设系统的状态是一个对测试很好的方法。 但是,在某些情况下,当你需要知道系统的状态到底是什么样,你可以做出断言或者实现更高标准的测试覆盖率。
所以根据你的需求,你可以通过以下方式之一来生成数据库测试数据:
  • 在已知的生产环境子项目上运行黑盒测试场景
  • 在运行测试用例之前,使用精心设计的数据存入数据库。
当然,黑盒测试并不意味着你不需要进行单元测试,你还是要写单元测试 for your APIs.
6 - 利用 JWT 做 无状态认证
由于你的 REST API 必须是无状态的,所以你的身份验证层也是如此。 为此,JWT(JSON Web Token)是理想化的。
JWT 由三部分组成:
  • Header 包含 token 的类型和散列算法
  • Payload 包含声明
  • Signature (JWT 没有加密附加数据,签名就可以!)
添加JWT-基于身份验证的应用程序非常简单:
const koa = require('koa') const jwt = require('koa-jwt')const app = koa()app.use(jwt({ secret: 'very-secret' }))// Protected middleware app.use(function *(){ // content of the token will be available on this.state.user this.body = { secret: '42' } })

之后,API 端受 JWT 的保护。 要访问受保护的接口,必须在授权标头中提供 token。
curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com

JWT 模块不依赖于任何数据库层,这一点你应该了解。这是因为所有的 JWT tokens 可以自己验证自己,并且它们可以保存时间来保存数据值。
另外,你必须确保所有 API 端只能通过使用 HTTPS 的安全连接进行访问。
在上一篇文章中,我们详细介绍了Web身份验证方法(https://blog.risingstack.com/web-authentication-methods-explained/) - 我建议大家查看一下!
7 - 使用条件请求
条件请求是根据特定 HTTP 标头执行的 HTTP 请求。 你可以将这些标头视为前提条件:如果满足要求,则请求将以不同的方式执行。
"Conditional requests are executed differently depending on specific HTTP headers" via @RisingStack
这些标头尝试检查存储在服务器上的资源的版本是否与相同资源的给定版本匹配。 因为这个原因,这些 headers 可以:
  • 是上次修改的时间
  • 也可能是一个实体标签,每个版本都不同
这些 headers 有:
  • Last-Modified(用于指出资源上次是什么时候修改的),
  • Etag (表示实体标签)
  • If-Modified-Since (与 Last-Modified 标头一起使用),
  • If-None-Match (与 Etag 标头一起使用),
我们一起看一个例子!
下面的客户端没有任何以前版本的 doc 资源,因此当资源发送时, If-Modified-SinceIf-None-Match 标头都不会被应用。 之后,服务器会响应 EtagLast-Modified 标头的设置。
构建|构建 Node.js REST API 的十条实践
文章图片
MDN Conditional request documentation.png 当它尝试请求相同的资源时,客户端可以设置 If-Modified-SinceIf-None-Match 标头,因为目前它只有一个版本。 如果响应相同,则服务器只需回复 304 - 未修改状态,不再发送资源。

构建|构建 Node.js REST API 的十条实践
文章图片
MDN Conditional request documentation2.png
8 - 接受率限制
速率限制用于控制给定消费者可以向 API 发送多少个请求。
如果要告知你的 API 用户还剩多少请求,请设置以下标头:
  • X-Rate-Limit-Limit 在给定时间间隔内允许的请求数,
  • X-Rate-Limit-Remaining 在同一间隔内剩余的请求数,
  • X-Rate-Limit-Reset 速率限制将被重置的时间.
大多数 HTTP 框架支持(或使用插件)。 例如,如果您使用Koa,则有[koa-ratelimit](https://github.com/koajs/ratelimit)模块。
注意,时间窗口可以根据不同的 API 提供者而有所不同 - 例如,GitHub 使用一小时,而 Twitter 15分钟。
9 - 创建一个正确的 API 文档
当你编写 API 时,其他人也可以使用它们,从中受益。 所以为你的 Node.js REST API 提供 API 文档至关重要。
以下开源项目可以帮助您为 API 创建文档:
API Blueprint
Swagger
或者,如果你想使用托管产品,你可以去 Apiary.
10 - 不要错过 API 的未来
在过去的几年中,有两种主要的 API 查询语言 - 来自Facebook的 GraphQL 和来自 Netflix 的Falcor。 但为什么我们需要他们?
现在我们来想象一下下面的 RESTful 资源请求:
/org/1/space/2/docs/1/collaborators?include=email&page=1&limit=10

由于您想要一直为所有模型获得相同的响应格式,所以这样很容易会失控。 而这就是 GraphQL 和
Falcor 可以帮助你的地方。
关于 GraphQL
GraphQL 是 API 的查询语言,也是用于使用现有数据来满足这些查询的运行时间。GraphQL 为API 提供了完整的、可理解的数据描述,使客户能够准确地询问他们需要什么,这样就更容易随着时间的推移发展 API,它还支持强大的开发工具。 - 更多阅读请点击 这里.
关于 Falcor Falco r是为 Netflix UI 提供支持的创新数据平台。 Falcor 允许你将所有后端数据建模为 Node 服务器上的单个虚拟 JSON 对象。如果你知道你的数据,你知道你的 API 的话,可以在客户端上,使用熟悉的 JavaScript 来操作(如get,set和call)来处理您的远程 JSON 对象。 - 更多阅读请点击 这里.
可以激发灵感的神奇的 REST API
如果您即将开始开发一个 Node.js REST API 或准备为旧版本创建一个新的版本,我们收集了四个值得一看的现实生活中的例子:
  • GitHub API
  • Twilio API
  • Stripe API
  • DigitalOcean API
希望看完这篇文章后,你可以更好地了解如何使用 Node.js编写 API。有任何问题请与我联系!

    推荐阅读