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

如果想要发送关于响应体数据的元数据,可以使用 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 支持。并且已经被 npm 和 netflix 用来在生产环境提供重要的服务。

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: BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.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

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

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

本文收藏于 https://segmentfault.com/a/1190000008537712

restful api编写规范的更多相关文章

  1. RESTful API 编写规范

    RESTful API 编写规范 在一个RESTful系统里,客户端向服务端发起索取资源的操作只能通过HTTP协议语义来进行交互.最常用的HTTP协议语义有以下5个: GET GET:发送一条或者多条 ...

  2. RESTful API 编写指南

    基于一些不错的RESTful开发组件,可以快速的开发出不错的RESTful API,但如果不了解开发规范的.健壮的RESTful API的基本面,即便优秀的RESTful开发组件摆在面前,也无法很好的 ...

  3. RESTful API接口文档规范小坑

    希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,谢谢关注. 前后端分离的开发模式,假如使用的是基于RESTful API的七层通讯协议,在联调的时候,如何避免配合过程中出现问 ...

  4. 深入理解 RESTful Api 架构

    转自https://mengkang.net/620.html 一些常见的误解 不要以为 RESTful Api  就是设计得像便于 SEO 的伪静态,例如一个 Api 的 URL 类似于 http: ...

  5. RESTful API 和 Django REST framework

    100天 cmdb最后一天 #RESTful API - 定义规范 如get就是请求题 - 面向资源编程 把网络任何东西都当作资源 #给一个url,根据方法的不同对资源做不同的操作 #返回结果和状态码 ...

  6. 使用Spring MVC开发RESTful API

    第3章 使用Spring MVC开发RESTful API Restful简介 第一印象 左侧是传统写法,右侧是RESTful写法 用url描述资源,而不是行为 用http方法描述行为,使用http状 ...

  7. 理解RESTful Api设计

    REST REST(REpresentational State Transfer)是 Roy Fielding 博士于 2000 年在他的博士论文中提出来的一种软件架构风格(一组架构约束条件和原则) ...

  8. 利用 Django REST framework 编写 RESTful API

    利用 Django REST framework 编写 RESTful API Updateat 2015/12/3: 增加 filter 最近在玩 Django,不得不说 rest_framewor ...

  9. Spring Boot 2.x 编写 RESTful API (三) 程序层次 & 数据传输

    用Spring Boot编写RESTful API 学习笔记 程序的层次结构 相邻层级的数据传输 JavaBean 有一个 public 的无参构造方法 属性 private,且可以通过 get.se ...

随机推荐

  1. 让UpdatePanel支持文件上传(2):服务器端组件 .

    我们现在来关注服务器端的组件.目前的主要问题是,我们如何让页面(事实上是ScriptManager控件)认为它接收到的是一个异步的回送?ScriptManager控件会在HTTP请求的Header中查 ...

  2. F5 IIS Log获取客户端源IP

    1.配置F5启用X-Forwarded-For方法: 1:Local Traffic-Profiles-Http-改"Insert XForwarded For"为Enable 2 ...

  3. Linux ping命令详解

    Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性 基于IMCP协议 常见命令参数 -q 不显示任何传送封包的信息,只显示最后的结果 -n 只输出数值 -R 记录路由过程 ...

  4. 对于over-posting的防御

    over-posting简单的说就是指用户通过猜测等手段得知了后端数据Model的属性名称,在数据更新或添加的时候提交了本不应该允许用户更改的数据库字段,并且在服务器端因为没有进行防御而将恶意提交的数 ...

  5. loadrunner测试c/s架构的应用系统

    用LoadRunner测试c/s架构的软件,怎样去选择协议,困扰了我很久,看到这篇文章,感觉有点收获,所以特意转了过来,希望对大家有用. 首先,什么是协议?协议无非就是一个约定,关于数据包发送的格式的 ...

  6. ZT pthread_detach

    pthread_detach 创 建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出 ...

  7. PhoneGap 加速计 api 获取加速计 x 轴 y 轴 z 轴信息

    一.PhoneGap 加速计 Accelerometer 对象介绍 1.主要方法 accelerometer.getCurrentAcceleration 获取当前设备在 x,y,z 轴上的加速度信息 ...

  8. Mac Sublime Text3快捷键

    下载地址http://www.sublimetext.com/3 一.安装Package Control 按Ctrl + ` 调出console,粘贴下列安装代码到底部命令行并回车: import u ...

  9. webpack中热模块更新

    Hot Module Replacement,热模块更新,很多时候会简写成HMR. "scripts": { "start": "webpack-de ...

  10. jQuery.mobile.changePage的参数

    选项 类型:对象 属性: allowSamePageTransition(默认值:假的) 类型:布尔 默认情况下,changePage()忽略请求更改为当前活动页面.将此选项设置为true,则允许该请 ...