原文: 这篇文章涵盖了使用Node.js开发REST APIs的最佳实践,包括如何定义路由、用户鉴权、黑盒测试和正确使用缓存头等。
虽然开发REST APIs是Node.js的主要用途之一,但是在我们使用Trace
帮助客户寻找应用中的问题时,我们发现很多开发者在开发REST API时依然存在许多问题。
所以我们希望以下的实践可以帮助到大家:
1. 使用HTTP方法和API路由
想象一下,当你正在使用Node.js开发users的CRUD接口,对于这样的业务HTTP早就有了充足的工具集:POST、GET、PUT、PATCH和DELETE。
作为一个最佳实践,你的API路由应该始终使用名词来命名。针对刚才的users来说,api命名应该类似下面这样:
- 创建用户:POST /user 或者 put /user/:id
- 获取用户:GET /user GET /user/:id
- 更新用户:PATCH /user/:id
- 删除用户:DELETE /user/:id
API应该始终使用名词来命名!
2. 正确使用HTTP状态码
如果请求一个接口时产生了错误,你应该在响应中使用正确的状态码:
- 2xx:一切正常
- 3xx:资源重定向
- 4xx:因为客户端的错误导致请求无法被响应
- 5xx:服务端错误
如果你用的是Express,设置状态码会非常简单:
res.status(500).send({ error: 'Internal server error happened'}).复制代码
3. 使用HTTP headers发送元数据
与请求体相关的元数据可以使用HTTP headers发送,例如:
- 分页信息
- 限流
- 用户鉴权
如果你需要在标题中设置任何自定义元数据,最好在它们前面加上X
。例如,如果你使用的是CSRF token,那么将它们命名为X-Csrf-Token
是一种常见的(但非标准的)方式。但是这种方式也已经被废弃。新API应尽最大努力不使用可能与其他应用程序冲突的标头名称。例如,OpenStack
使用OpenStack
为其标头添加前缀:
OpenStack-Identity-Account-IDOpenStack-Networking-Host-NameOpenStack-Object-Storage-Policy复制代码
请注意,HTTP标准没有定义headers的任何大小限制;但是在实际使用中发现,Node.js(撰写本文时)对header对象强加了80KB的大小限制。
4. 使用合适的框架来开发REST APIs
选择最适合你的项目的框架非常重要。
Express, Koa or Hapi
Express
,Koa
和Hapi
可用于创建浏览器应用程序,因此,它们支持模板和渲染-仅举几个功能。如果您的应用程序也需要提供面向用户的一面,那么为它们提供帮助是有意义的。
Restify
另一方面,Restify
专注于帮助您构建REST服务。它的存在是为了让您构建可维护和可观察的“严格”API服务。Restify还为所有处理程序提供自动DTrace支持。
5. 对你的REST APIs进行黑盒测试
测试REST API的最佳方法之一是将它们视为黑盒子。
黑盒测试是一种测试应用程序功能的方法,在不了解其内部结构或工作原理的情况下进行检查。因此,没有任何依赖项被模拟或存根,但系统作为一个整体进行测试。
其中一个可以帮助您进行黑盒测试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编写单元测试。
6. 做基于JWT的无状态认证
由于您的REST API必须是无状态的,因此您的身份验证层也是如此。为此,JWT(JSON Web Token)是理想的选择。
JWT由三部分组成:
- Header:包含令牌的类型和散列算法
- Payload:包含声明
- Signature:JWT不加密有效载荷,必须签名!
将基于JWT的身份验证添加到您的应用程序非常简单:
const koa = require('koa')const jwt = require('koa-jwt')const app = koa()app.use(jwt({ secret: 'very-secret' }))// Protected middlewareapp.use(function *(){ // content of the token will be available on this.state.user this.body = { secret: '42' }})复制代码
之后,API受JWT保护。要访问受保护的端点,您必须在Authorization标头字段中提供令牌。
curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com 复制代码
您可以注意到的一件事是JWT模块不依赖于任何数据库层。情况就是这样,因为所有JWT令牌都可以自己验证,并且它们也可以包含生存时间值。
此外,您始终必须确保只能通过使用HTTPS的安全连接访问所有API端点。
7. 使用条件请求
条件请求是HTTP请求,根据特定的HTTP标头执行不同的HTTP请求。您可以将这些标头视为先决条件:如果满足这些标头,则将以不同的方式执行请求。这些标头尝试检查服务器上存储的资源版本是否与同一资源的给定版本匹配。由于这个原因,这些标题可以是:
- 最后一次修改的时间戳
- 或实体标签,每个版本都有所不同
这些标签包括:
- Last-Modified (标明资源上次修改的时间)
- Etag (标明实体标签)
- If-Modified-Since (使用Last-Modified)
- If-None-Match (使用Etag)
8. 拥抱限流
用于控制给定消费者可以向API发送的请求数量。
要告诉您的API用户他们剩下多少请求,请设置以下标头:
- X-Rate-Limit-Limit, 给定时间间隔内允许的请求数
- X-Rate-Limit-Remaining, 相同时间段内剩下的请求数
- X-Rate-Limit-Reset, 请求数重置时间
大多数HTTP框架都支持开箱即用(或使用插件)。例如,如果您使用Koa,则有koa-ratelimit包。
9. 创建优雅的接口文档
您编写API以便其他人可以使用它们从中受益。为Node.js REST API提供API文档至关重要。以下开源项目可以帮助您为API创建文档:
- API Blueprint
- Swagger 或者,如果您想使用托管产品,您可以选择Apiary。
10. 不要忘记接口的将来使用者
在过去几年中,出现了两种主要的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允许您将所有后端数据建模为节点服务器上的单个Virtual JSON对象。在客户端上,您可以使用熟悉的JavaScript操作(如get,set和call)来处理远程JSON对象。如果您了解自己的数据,就会知道自己的API。
我希望你现在能更好地理解如何使用Node.js编写API。