在这篇文章里, 我们将介绍 Node.JS REST API 的最佳实践, 包括关于路由命名, 身份认证, 黑盒测试, 使用恰当的网络缓存等内容.
一个最流行的 Node.JS RESTful API 监听工具 Trace https://trace.risingstack.com/ , 通过 Trace, 我们帮助我们的用户寻找程序中的问题. 我们的经验告诉我们, 开发者开发 REST API 时有很多问题.
我希望这些用在 RisingStack 上的这些最佳实践能够帮助大家.
1 使用 HTTP 方法和 API 路由
想象一下, 你写的 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 删除用户
API 路由必须是一个名词作为资源标识符
点击跳到 Tweet
2 正确使用 HTTP 状态码
当请求出现错误的时候, 你必须返回相应的状态码:
2xx, 一切正常
3xx, 资源被移除
4xx, 客户端错误 (比如请求一个不存在的资源)
5xx, API(服务端)错误 (比如出现异常)
如果你使用 Express, 设置状态码非常简单 res.status(500).send({error: 'Internal server error happened'}),Restify 框架的写法也差不多 res.status(201).
如果想查看完整版 , 点击 HTTP 状态码清单
3 使用 HTTP 头发送元数据(Metadata)
在 HTTP header 上添加关于有效载荷 (payload) 的元数据(metadata), 适用于以下场景:
分页
限制访问频率
身份验证
一系列的标准化 HTTP header 可参照此链接
如果你需要在你的 HTTP header 里设置一些定制的元数据, 最佳实践是在定制内容前加 X. 举个例子, 如果你在使用 CSRF token, 通用的命名方法 (不是规范) 是命名为 X-Csrf-Token. 当然通过 RFC 6648 https://tools.ietf.org/html/rfc6648 的方式被弃用. 创建的 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)', 攻击者会填入一个无法加载完的请求头, 让程序进入一个永远在加载中的状态."
选自 Node.JS HTTP parser
4 选择一个合适的框架实现 Node.JS REST API
选择最适合项目应用场景的, 才是最重要的.
Express, Koa 还是 Hapi
Express http://expressjs.com/ ,Koa http://koajs.com/ 和 Hapi http://hapijs.com/ 都可以支持浏览器调用, 并且他们都支持模板和后端渲染, 这里我们之罗列一小部分他们的特性. 如果你的应用需要面向用户(界面), 这些特性对他们是有意义的.
Restify
另一面, Restify http://restify.com/ 是一个专注于构建 REST 服务的库. 它强制你使用严格模式构建你的 API 服务, 以此保证整个系统的可维护性和可监控性. Restify 也带有自动化工具 DTrace http://dtrace.org/blogs/about/ 支持, 动态监测你的程序.
Restify 也被大量产品用在主程序里, 比如 https://npmjs.com/ 和 Netflix https://netflix.com/ .
"Restify 让你使用严格模式提供 API 服务, 保持程序的可维护性和可监控性
点击跳到 Tweet
5 对 Node.JS REST API 进行黑盒测试
检验你的 REST API 的最佳方式是进行黑盒测试.
黑盒测试是一种测试方法, 就是排除任何主观因素和已知条件, 检测每个功能是否都能正常使用. 所以没有任何外部依赖是假数据或者桩代码(stub), 整个程序是看一个整体.
一个可以帮助你进行 Node.JS REST API 黑盒测试的工具 https://www.npmjs.com/package/supertest .
一个简单的用来检验用户返回数据的用例, 如果使用 https://mochajs.org/ 完成的话, 如下:
- 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.
使用 RisingStack 的程序监听和 Debug 专家
通过 Trace 提高 REST API 质量
学习更多
6 使用 JWT-Based, 无状态身份认证
你的 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 安全认证方法 .
7 使用条件请求
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 作为先决条件: 当条件符合, 就会获得相应的返回.
对于条件请求, 返回什么结果取决于 HTTP header
点击跳到 Tweet
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 条件请求文档
8 请求频率限制
请求频率限制用于控制 API 可以被消费多少次.
可以通过设置 HTTP header 告诉客户端还有多少请求可以被消费:
X-Rate-Limit-Limit 同一个时间段所允许的请求的最大数目
X-Rate-Limit-Remaining 在当前时间段内剩余的请求的数量
X-Rate-Limit-Reset 为了得到最大请求数所等待的秒数
大部分 HTTP 框架支持这种写法(或通过插件支持). 举个例子, 如果你用 Koa, 可以使用 koa-ratelimit https://github.com/koajs/ratelimit 这个包.
注意, 请求时间间隔可以根据 API 不同, 设置不同. 比如 GitHub 时间间隔是 1 小时, Twitter 是 15 分钟.
9 创建一个合适的 API 文档
你写的 API 是给别人用的, 要对别人有价值. 提供一个 API 文档是十分重要的.
以下开源项目可以帮助你创建 API 文档:
- API Blueprint https://apiblueprint.org/
- Swagger http://swagger.io/
如果你想使用 API 自动生成工具, 推荐 Apiary https://apiary.io/ .
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, 成为更强大的开发工具. 了解更多 http://graphql.org/
关于 Falcor
Falcor 一个创新的数据抓取哭, 支撑着 Netflix UI.Falcor 允许你通过一个 Node 服务上的虚拟 JSON 操作任何后台数据. 在客户端, 使用远程 JSON 对象, 通过 get,set, call 查找数据, 数据就是 API . 了解更多 https://netflix.github.io/falcor
令人惊讶的 REST API 灵感
如果你正想要开发 Node.JS REST API 或者更新一个版本, 我们收集了 4 个有价值的, 现实中的例子:
- GitHub API https://developer.github.com/v3/
- Twilio API https://www.twilio.com/docs/api/rest
- Stripe API https://stripe.com/docs/api
- DigitalOcean API