乐博娱乐»NodeJS»编写 Node.js Rest API 的 10 个最佳实践

编写 Node.js Rest API 的 10 个最佳实践

来源:王仕军 宣布时间:2017-03-03 阅读次数:乐博

  Node.js 除了用来编写 WEB 应用之外,还可以用来编写 API 服务,我们在本文中会介绍编写 Node.js Rest API 的最佳实践,包罗如何命名路由、进行认证和测试等话题,内容摘要如下:

  1. 正确使用 HTTP Method 和路由
  2. 正确的使用 HTTP 状态码
  3. 使用 HTTP Header 来发送元数据
  4. 为 REST API 挑选合适的框架
  5. 要对 API 进行黑盒测试
  6. 使用基于 JWT 的无状态的认证机制
  7. 学会使用条件请求机制
  8. 拥抱接口调用频率限制(Rate-Limiting)
  9. 编写良好的 API 文档
  10. 对 API 技术演化保持关注

乐博

 1. 正确使用 HTTP Method 和路由

  试想你正要构建一个 API 用来创建、更新、获取、删除用户,对于这些操作,HTTP 规范里面已经有了现成的操作:POST、PUT、GET、DELETE,建议直接使用他们来描述接口的行为。

  至于路由的命名,应该使用名词或名词性短语来作为资源标识符,好比上文提到的用户治理的例子,路由就应该长这样:

  • POST /users 或者 PUT /users/:id 用来创建新用户;
  • GET /users 用来获取用户列表;
  • GET /users/:id 用来获取单个用户;
  • PATCH /users/:id 用来更新用户信息;
  • DELETE /users/:id 用来删除用户;

 2. 正确的使用 HTTP 状态码

  如果服务器端在请求处置惩罚的历程中堕落了,你必须设置正确的响应状态码,具体如下:

  • 2xx,体现一切正常;
  • 3xx,体现资源位置已经更改;
  • 4xx,体现因为客户端错误而导致请求无法被处置惩罚,好比参数校验没通过;
  • 5xx,体现因为服务器错误导致请求无法被处置惩罚,好比服务端抛了异常;

  如果你使用 express,设置状态码很是简朴:res.status(500).send({ error: 'Internal server error happend' }),如果使用了 restify,也是类似的:res.status(201)。

  如果想看完整的 HTTP 状态码,点击这里

 3. 使用 HTTP Header 来发送元数据

  如果想要发送关于响应体数据的元数据,可以使用 Header ,Header 可以包罗的常见元数据包罗如下几类:

  • 分页信息;
  • 频率限制信息;
  • 认证信息;

  如果你需要在 Header 中发送自界说的元数据,最好的做法是在 Header 名称前面加 X,例如,需要发送 CSRF Token 的时候,实际的 Header 应该命名为:X-CSRF-Token,然而,这种 Header 在 RFC 6648 中已经被废弃了。API 在设置自界说 Header 的时候还要尽可能制止命名冲突,好比为了到达这个目的OpenStack 为所有 API 的自界说 Header 都加上了 OpenStack 的前缀:

OpenStack-Identity-Account-ID  
OpenStack-Networking-Host-Name  
OpenStack-Object-Storage-Policy

  需要注意的是,虽然 HTTP 规范中没有划定 Header 的巨细,但是 Node.js 中 Header 的巨细被限制在了 80KB。官方原文如下:

不要让 HTTP Header ,包罗其中状态码那行的整体巨细凌驾 HTTP_MAX_Header_SIZE,这样做的目的是为了防御基于 Header 的 DDOS 攻击。点击这里

 4. 为 REST API 挑选合适的框架

  凭据你的实际场景挑选合适的框架是很是重要的,Node.js 中的框架大致介绍如下:

  Express、Koa、HAPI

  ExpressKoaHAPI 主要是用来构建浏览器 WEB 应用,因为他们都支持服务端模板渲染,虽然这只是他们众多功效中的一个。如果你的应用需要提供用户界面,那么这三个就是不错的选择。

  Restify

  而 Restify 是专门用来创建切合 REST 规范的服务的,他降生的目的就是帮你构建严格意义上的、可维护的 API 服务。Restify 内置了所有请求处置惩罚函数的 DTrace 支持。而且已经被 npmnetflix 用来在生产情况提供重要的服务。

 5. 要对 API 进行黑盒测试

  测试 API 的最好措施是对他们进行黑盒测试,黑盒测试是一种不体贴应用内部结构和事情原理的测试要领,测试时系统任何部门都不应该被 mock。

  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);
  });
});

  可能有人会问:API 服务所连接的数据库里面的数据是如何写进去的呢?

  通常来说,你写测试的时候,要尽可能差池系统状态做假设,然而在某些场景下,你需要准确的知道系统当前所处的状态以增加更多的断言来提高测试笼罩率。如果你有这种需求,你可以试用如下的要领对数据库进行预填充:

  • 选择生产情况数据的子集来运行黑盒测试;
  • 运行黑盒测试之前把手工结构的数据填充到数据库中。

  此外,有了黑盒测试并不意味着不需要单元测试,针对 API 的单元测试照旧需要编写的。

 6. 使用基于 JWT 的无状态的认证机制

  因为 Rest API 必须是无状态的,因此认证机制也需要是无状态的,而基于 JWT(JSON Web Token) 的认证机制是无状态认证机制中的最佳解决方案。

  JWT 的认证机制包罗三部门:

  1. Header:包罗 token 的类型和哈希算法;
  2. payload:包罗声明信息;
  3. signature:JWT 实际上并不是对 payload 进行加密,只是对其做了签名;

  为 API 添加基于 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 的掩护。如果要会见这种被掩护的接口,需要使用 Authorization Header 来提供 token,好比:

curl --Header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com

  你可能注意到了,JWT 模块并不依赖任何数据存储层,这是因为 token 自己是可以单独被校验的,token 里面的 payload 甚至可以包罗 token 的签名时间、有效期限。

  此外,你还需要确保,所有的 API 接口只能通过更宁静的 HTTPS 链接来会见。

 7. 学会使用条件请求机制

  条件请求机制是基于差异的 Header 体现出差异的行为的机制,可以认为这些 Header 就是请求处置惩罚方式的先决条件,如果条件满足,请求处置惩罚方式就会有所差异。

  可以利用这些 Header 检测服务器上的资源版本是否匹配特定的资源版本,这些 Header 的取值可以是如下的内容:

  • 资源的最后修改时间;
  • 资源的标签(随资源变化而变化);

  具体来说:

  • Last-Modified:标识资源的最新修改时间;
  • Etag:标识资源的标签;
  • If-Modified-Since:结合 Last-Modified Header 使用;
  • If-Non-Match:结合 Etag 使用;

  下面来看一个实际的例子:

  客户端不知道 doc 资源的任何版本,所以请求时即不能提供 If-Modified-Since,也不能提供 If-Non-Match 两个 Header,然后服务端在响应中会增加 Etag 和 Last-Modified 两个 Header。

  nodejs-resftul-api-with-conditional-request-without-previous-versions.png

  接下来,客户端再次请求相同的资源的时候,就可以带上 If-Modified-Since 和 If-Non-Match 这两个 Header 了,然后如果服务器端会检查资源是否修改,如果没有修改,直接返回 304 - Not Modified 状态码,而不重复发送资源的内容。

  nodejs-resftul-api-with-conditional-request-with-previous-versions.png

 8. 拥抱接口调用频率限制(Rate-Limiting)

  频率限制是用来控制调用方有对接口提倡请求的次数,为了让你的 API 用户知道他们还剩下几多余额,可以设置下面的 Header:

  • X-Rate-Limit-Limit:特定时间段内允许的最多请求次数;
  • X-Rate-Limit-Remaining:特定时间段内剩余的请求次数;
  • X-Rate-Limit-Reset:什么时候请求频率限制次数会重置;

  大多数的 WEB 框架都支持上面这些 Header,如果内置不支持,也可以找到插件来支持,好比,如果你使用了 koa,可以使用 koa-rate-limit

  需要注意的是,差异的 API 服务提供商频率限制的时间窗差异会很大,好比 GitHub 是 60 分钟,而 Twitter 是 15 分钟。

 9. 编写良好的 API 文档

  编写 API 的目的虽然是让别人使用并受益,提供良好的接口文档至关重要。下面这两个开源项目可以帮你创建 API 文档:

  如果你愿意使用第三方文档服务商,可以考虑 Apiary

 10. 对 API 技术演化保持关注

  已往几年中,API 技术方案领域泛起了两种新的查询语言,划分是 Facebook 的 GraphQL 和 Netflix 的 Falcor,为什么需要他们呢?

  试想这种 API 接口请求:/org/1/space/2/docs/1/collaborators?include=email&page=1&limit=10,类似的情况会让 API 很快失控,如果你希望所有接口能返回类似的响应花样,那么 GraphQL 和 Falcor 就能帮你解决这个问题。

  关于 GraphQL

GraphQL 是一种用于 API 的查询语言,也是一种基于现有数据处置惩罚数据查询的运行时。GraphQL 为您的 API 中的数据提供了一个完整和可理解的描述,使用户能够准确地询问他们需要什么,使得随着时间推移的 API 演化更容易,GraphQL 另有强大的乐博娱乐开发工具支持。 到这里阅读更多。

  关于 Falcor

Falcor 是支撑着 Netflix UI 的创新数据平台。Falcor 允许你将所有后端数据建模为 Node.js 服务商的单个虚拟 JSON 工具。在客户端可以使用熟悉的 JavaScript 操作、处置惩罚远程JSON工具。如果你知道你的数据,你就知道你的 API 长啥样。 到这里阅读更多。

 能带来灵感的优秀 API 设计

  如果你正在乐博娱乐开发 Rest API 或者准备革新老版本的 API,这里收集了几个在线上提供服务、设计优秀而且很是直接借鉴的 API:

  希望读到这里的同学对如何用 Node.js 编写良好的 API 有更好的理解,如果有建议,接待评论中提出。

  英文原文:https://blog.risingstack.com/10-best-practices-for-writing-node-js-rest-apis/