10個關于Node.js REST API 的最佳實踐
在這篇文章里,我們將介紹Node.js REST API的最佳實踐,包括關于路由命名,身份認證,黑盒測試,使用恰當的網絡緩存等內容。
一個最流行的Node.js RESTful API監聽工具 Trace ,通過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 刪除用戶
2 正確使用HTTP狀態碼
當請求出現錯誤的時候,你必須返回相應的狀態碼:
-
2xx , 一切正常
-
3xx , 資源被移除
-
4xx , 客戶端錯誤 (比如請求一個不存在的資源)
-
5xx , API(服務端)錯誤 (比如出現異常)
如果你使用Express,設置狀態碼非常簡單 res.status(500).send({error: 'Internal server error happened'}) ,Restify框架的寫法也差不多 res.status(201) 。
3 使用HTTP頭發送元數據(Metadata)
在HTTP header上添加關于有效載荷(payload)的元數據(metadata),適用于以下場景:
-
分頁
-
限制訪問頻率
-
身份驗證
如果你需要在你的HTTP header里設置一些定制的元數據,最佳實踐是在定制內容前加 X 。 舉個例子,如果你在使用CSRF token,通用的命名方法(不是規范)是命名為 X-Csrf-Token 。當然通過 RFC 6648 的方式被棄用。創建的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)’,攻擊者會填入一個無法加載完的請求頭,讓程序進入一個永遠在加載中的狀態。"
4 選擇一個合適的框架實現 Node.js REST API
選擇最適合項目應用場景的,才是最重要的。
Express, Koa 還是 Hapi
Express , Koa 和 Hapi 都可以支持瀏覽器調用,并且他們都支持模板和后端渲染,這里我們之羅列一小部分他們的特性。如果你的應用需要面向用戶(界面),這些特性對他們是有意義的。
Restify
另一面, Restify 是一個專注于構建REST服務的庫。它強制你使用嚴格模式構建你的API服務,以此保證整個系統的可維護性和可監控性。 Restify也帶有自動化工具 DTrace 支持,動態監測你的程序。
Restify也被大量產品用在主程序里,比如 npm 和 Netflix 。
5 對 Node.js REST API 進行黑盒測試
檢驗你的REST API的最佳方式是進行黑盒測試。
黑盒測試是一種測試方法,就是排除任何主觀因素和已知條件,檢測每個功能是否都能正常使用。所以沒有任何外部依賴是假數據或者樁代碼(stub),整個程序是看一個整體。
一個可以幫助你進行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寫單元測試腳本 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安全。
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作為先決條件:當條件符合,就會獲得相應的返回。
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 。
如果在請求的時候,客戶端設置了 If-Modified-Since 和 If-None-Match ,一旦請求這個資源, 這個驗證這個資源的版本。如果版本相同,服務器只是回應 304 - Not Modified 狀態,不返回其他信息。
8 請求頻率限制
請求頻率限制用于控制API可以被消費多少次。
可以通過設置HTTP header告訴客戶端還有多少請求可以被消費:
-
X-Rate-Limit-Limit 同一個時間段所允許的請求的最大數目
-
X-Rate-Limit-Remaining 在當前時間段內剩余的請求的數量
-
X-Rate-Limit-Reset 為了得到最大請求數所等待的秒數
大部分HTTP框架支持這種寫法(或通過插件支持)。舉個例子,如果你用Koa,可以使用 koa-ratelimit 這個包。
注意,請求時間間隔可以根據API不同,設置不同。比如 GitHub時間間隔是1小時,推ter是15分鐘。
9 創建一個合適的API文檔
你寫的API是給別人用的,要對別人有價值。提供一個API文檔是十分重要的。
以下開源項目可以幫助你創建API文檔:
10 不要錯過未來的API
在過去的幾年中,出現了兩個API查詢語言,非死book的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,成為更強大的開發工具。
關于Falcor
Falcor一個創新的數據抓取哭,支撐著Netflix UI。Falcor允許你通過一個Node服務上的虛擬JSON操作任何后臺數據。在客戶端,使用遠程JSON對象,通過get,set, call查找數據,數據就是API 。
令人驚訝的REST API靈感
如果你正想要開發Node.js REST API或者更新一個版本,我們收集了4個有價值的,現實中的例子:
來自:http://www.w3ctech.com/topic/1968