构建 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 最受欢迎的一个用法是使用它构建 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, KoaHapi 可用于创建浏览器应用程序,比如它们支持模板和渲染。 如果您的应用程序需要提供面向用户的服务,那么就尽管去使用这些框架吧。

Restify

另一方面,Restify (http://restify.com/) 专注于帮你构建 REST 服务。它就是为了让你构建具有可维护性和可观察性的 “严格的” 的API 服务。Restify 还提供了自动的DTrace(http://dtrace.org/blogs/about/) 支持所有的处理程序。

Restify 也被用在一些主要的产品中,比如 npmNetflix

"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 标头的设置。

MDN Conditional request documentation.png

当它尝试请求相同的资源时,客户端可以设置 If-Modified-SinceIf-None-Match 标头,因为目前它只有一个版本。 如果响应相同,则服务器只需回复 304 - 未修改状态,不再发送资源。

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 或准备为旧版本创建一个新的版本,我们收集了四个值得一看的现实生活中的例子:

希望看完这篇文章后,你可以更好地了解如何使用 Node.js编写 API。有任何问题请与我联系!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,410评论 0 19
  • 1 这是《月亮与六便士》中“我”一直对男主人公说的话。外国小说中的人名总是太长,很难让人记住。 看完这本300多页...
    莲苇阅读 680评论 0 2
  • 最近一段时间,我加入了弟媳的宝宝爱阅读微群,看到群里的妈妈们那么用心地培养孩子的阅读兴趣,真是羡慕至极!读到妈妈们...
    心草阅读 322评论 0 1
  • 那是在1995年的春天,在大学毕业半年后我调到了局团委工作担任团委干事。整个局团委除了团委谷书记就我一个小干事了。...
    格思阿甘阅读 379评论 5 8