本文转载自:众成翻译 译者:净化 链接:http://www.zcfy.cc/article/2608 原文:https://blog.risingstack.com/10-best-practices-for-writing-node-js-rest-apis/
在这篇文章里,我们将介绍Node.js REST API的最佳实践,包括关于路由命名,身份认证,黑盒测试,使用恰当的网络缓存等内容。
一个最流行的Node.js RESTful API监听工具Trace,通过Trace,我们帮助我们的用户寻找程序中的问题。我们的经验告诉我们,开发者开发REST API时有很多问题。
我希望这些用在 RisingStack上的这些最佳实践能够帮助大家。
想象一下,你写的Node.js RESTful API可以用于新建,更新,检索或者删除用户。对于这些业务操作,HTTP已经有了成套的工具箱:POST
, PUT
, GET
, PATCH
或 DELETE
。
作为最佳实践,你的API路由应该使用名词作为资源标识符 。说到关于用户的资源,你可以像下面这样构建:
POST /user
或 PUT /user:/id
创建新用户
GET /user
查找一些用户
GET /user/:id
查找某个用户
PATCH /user/:id
编辑修改一个存在用户的信息
DELETE /user/:id
删除用户
当请求出现错误的时候,你必须返回相应的状态码:
2xx
, 一切正常
3xx
, 资源被移除
4xx
, 客户端错误 (比如请求一个不存在的资源)
5xx
, API(服务端)错误 (比如出现异常)
如果你使用Express,设置状态码非常简单 res.status(500).send({error: 'Internal server error happened'})
,Restify框架的写法也差不多res.status(201)
。
如果想查看完整版 ,点击HTTP状态码清单
在HTTP header上添加关于有效载荷(payload)的元数据(metadata),适用于以下场景:
分页
限制访问频率
身份验证
一系列的标准化HTTP header可参照此链接
如果你需要在你的HTTP header里设置一些定制的元数据,最佳实践是在定制内容前加 X
。 举个例子,如果你在使用CSRF token,通用的命名方法(不是规范)是命名为X-Csrf-Token
。当然通过RFC 6648的方式被弃用。创建的API时,要尽最大的可能避免命名冲突。比如,OpenStack在命名HTTP header时,以OpenStack
开头:
OpenStack-Identity-Account-ID
OpenStack-Networking-Host-Name
OpenStack-Object-Storage-Policy
however, Node.js (as of writing this article) imposes an 80KB size limit on the headers object for practical reasons.
注意,HTTP规范并没有对HTTP header大小进行限制;尽管如此,出于对应用场景的考虑,作者希望对HTTP header进行80KB的大小限制。
" 不要允许设置任意大小的 HTTP header (包括状态行) ,不要超过
HTTP_MAX_HEADER_SIZE
的限制。这种检验师为了保护程序,防止‘阻断服务攻击(denial-of-service attack)’,攻击者会填入一个无法加载完的请求头,让程序进入一个永远在加载中的状态。"
选择最适合项目应用场景的,才是最重要的。
Express,Koa 和 Hapi 都可以支持浏览器调用,并且他们都支持模板和后端渲染,这里我们之罗列一小部分他们的特性。如果你的应用需要面向用户(界面),这些特性对他们是有意义的。
另一面,Restify是一个专注于构建REST服务的库。它强制你使用严格模式构建你的API服务,以此保证整个系统的可维护性和可监控性。 Restify也带有自动化工具 DTrace支持,动态监测你的程序。
Restify也被大量产品用在主程序里,比如npm和Netflix。
"Restify 让你使用严格模式提供 API 服务,保持程序的可维护性和可监控性
检验你的REST API的最佳方式是进行黑盒测试。
黑盒测试是一种测试方法,就是排除任何主观因素和已知条件,检测每个功能是否都能正常使用。 所以没有任何外部依赖是假数据或者桩代码(stub),整个程序是看一个整体。
一个可以帮助你进行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插入进数据库的?
通常,一个好的写测试用例的方法,就是尽可能减少假设的状态。并且,在某些场景中,当你需要知道系统状态的时候,黑盒测试可以发现系统设计的盲点,所以你可以通过断言,实现更高的测试覆盖率。
所以,根据你的需要,可以用下列方式之一,用测试数据填充数据库:
在一个已知的数据库的子集,运行黑盒测试脚本
新建一个填充了数据的数据库,运行黑盒测试脚本
当然,黑盒测试不意味这你不需要单元测试,你也需要给你的API写单元测试脚本unit tests。
你的REST API必须是无状态的,身份认证也一样。JWT (JSON Web Token) 是个经典的解决方案。
JWT 由三部分构成:
Header,包含token的类型和哈希算法
Payload,包括断言
Signature (JWT 不加密 payload,只签名)
在你的程序中加入 JWT-based 非常简单:
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保护。访问受保护的端,你必须在请求头Authorization
字段提供token。
`curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com`
你可能注意到了,JWT不依赖任何数据层。因为JWT可以通过tokens自我验证,请求也可以包含请求有效时间。
当然,你也可以通过HTTPS来保护你的API安全。
推荐一篇有价值的文章,详解web安全认证方法 。
You can think of these headers as preconditions: if they are met, the requests will be executed in a different way.
条件请求根据HTTP header不同,返回不同。你可以把这些HTTP header作为先决条件:当条件符合,就会获得相应的返回。
These headers try to check whether a version of a resource stored on the server matches a given version of the same resource. Because of this reason, these headers can be:
这些header希望检测,这一版本的资源是否存在服务端,并返回对应版本的资源。所以,header可能包括如下信息:
最后一次修改的时间戳
一个标识不同版本entity tag
对应API:
Last-Modified
(标识当前资源最后修改时间)
Etag
(实体标签,标识版本)
If-Modified-Since
(和Last-Modified
一起使用)
If-None-Match
(和Etag
一起使用)
一个例子
The client below did not have any previous versions of the doc
resource, so neither the If-Modified-Since
, nor the If-None-Match
header was applied when the resource was sent. Then, the server responds with the Etag
and Last-Modified
headers properly set.
下面,客户端先前没有任何关于 doc
资源的版本,所以在发送请求时没有If-Modified-Since
, 和 If-None-Match
信息。之后服务端返回资源,并在响应头里写Etag
和Last-Modified
。
引自:MDN条件请求文档
如果在请求的时候,客户端设置了If-Modified-Since
和If-None-Match
,一旦请求这个资源, 这个验证这个资源的版本。如果版本相同,服务器只是回应304 - Not Modified
状态,不返回其他信息。
引自 MDN条件请求文档
请求频率限制用于控制API可以被消费多少次。
可以通过设置HTTP header告诉客户端还有多少请求可以被消费:
X-Rate-Limit-Limit
同一个时间段所允许的请求的最大数目
X-Rate-Limit-Remaining
在当前时间段内剩余的请求的数量
X-Rate-Limit-Reset
为了得到最大请求数所等待的秒数
大部分HTTP框架支持这种写法(或通过插件支持)。举个例子,如果你用Koa,可以使用 koa-ratelimit这个包。
注意,请求时间间隔可以根据API不同,设置不同。比如 GitHub时间间隔是1小时,Twitter是15分钟。
你写的API是给别人用的,要对别人有价值。提供一个API文档是十分重要的。
以下开源项目可以帮助你创建API文档:
如果你想使用API自动生成工具,推荐 Apiary。
在过去的几年中,出现了两个API查询语言,Facebook的GraphQL和Netflix的Falcor。但我们为什么需要它们?
想象以下RESTful资源请求:
`/org/1/space/2/docs/1/collaborators?include=email&page=1&limit=10`
这很容易失控——如果你希望按照相同的数据结构返回数据。这是GraphQL和Falcor解决的问题。
GraphQL是一门API查询语言,运行时为完成这些查询现有数据。GraphQL提供了一套完整的,易于理解的,可以描述数据的API。让客户端有了完整描述自己需要的数据的能力,随着时间的推移,这种方式可能演变成API,成为更强大的开发工具。了解更多
Falcor一个创新的数据抓取哭,支撑着Netflix UI。Falcor允许你通过一个Node服务上的虚拟JSON操作任何后台数据。在客户端,使用远程JSON对象,通过get,set, call查找数据,数据就是API 。了解更多
如果你正想要开发Node.js REST API或者更新一个版本,我们收集了4个有价值的,现实中的例子:
我希望此刻你知道应该怎么构建Node.js API,如果我漏写了什么,请在评论区告知。
扫码关注w3ctech微信公众号
共收到0条回复