Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all articles
Browse latest Browse all 14821

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

$
0
0

本文首发知乎专栏,全文共 6953 字,读完需 8 分钟,速读需 2 分钟。翻译自:RingStack的文章 https://blog.risingstack.com/10-best-practices-for-writing-node-js-rest-apis/,英文好的同学可以直接阅读原文,译文较原文有删节。

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规范里面已经有了现成的操作:POSTPUTGETDELETE,建议直接使用他们来描述接口的行为。

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

  • 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来发送元数据

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

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

如果你需要在 Header中发送自定义的元数据,最好的做法是在 Header名称前面加 X,例如,需要发送 CSRF Token的时候,实际的 Header应该命名为:X-CSRF-Token,然而,这种 HeaderRFC 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,这样做的目的是为了防御基于 HeaderDDOS攻击。点击这里

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. signatureJWT实际上并不是对 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,然后服务端在响应中会增加 EtagLast-Modified两个 Header

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

接下来,客户端再次请求相同的资源的时候,就可以带上 If-Modified-SinceIf-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技术方案领域出现了两种新的查询语言,分别是 FacebookGraphQLNetflixFalcor,为什么需要他们呢?

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

关于 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有更好的理解,如果有建议,欢迎评论中提出。

One More Thing

想直接在微信中订阅前端周刊?扫下方二维码关注前端周刊订阅号。 feweekly

想和我面对面交流?扫下方二维码添加我为好友。

wangshijun

Happy Hacking


Viewing all articles
Browse latest Browse all 14821

Trending Articles