編寫 Node.js Rest API 的 10 個最佳實踐

全文共 6953 字,讀完需 8 分鐘,速讀需 2 分鐘。翻譯自:RingStack 的文章 10 Best Practices for Writing Node.js REST APIs | @RisingStack,英文好的同學可以直接閱讀原文,譯文較原文有刪節,。

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 nOpenStack-Networking-Host-Name nOpenStack-Object-Storage-Policyn

需要注意的是,雖然 HTTP 規範中沒有規定 Header 的大小,但是 Node.js 中 Header 的大小被限制在了 80KB。官方原文如下:

不要讓 HTTP Header ,包括其中狀態碼那行的整體大小超過 HTTP_MAX_Header_SIZE,這樣做的目的是為了防禦基於 Header 的 DDOS 攻擊。點擊這裡

4. 為 REST API 挑選合適的框架

根據你的實際場景挑選合適的框架是非常重要的,Node.js 中的框架大致介紹如下:

Express、Koa、HAPI

Express、Koa、HAPI 主要是用來構建瀏覽器 WEB 應用,因為他們都支持服務端模板渲染,雖然這只是他們眾多功能中的一個。如果你的應用需要提供用戶界面,那麼這三個就是不錯的選擇。

Restify

而 Restify 是專門用來創建符合 REST 規範的服務的,他誕生的目的就是幫你構建嚴格意義上的、可維護的 API 服務。Restify 內置了所有請求處理函數的 DTrace 支持。並且已經被 npm 和 netflix 用來在生產環境提供重要的服務。

5. 要對 API 進行黑盒測試

測試 API 的最好辦法是對他們進行黑盒測試,黑盒測試是一種不關心應用內部結構和工作原理的測試方法,測試時系統任何部分都不應該被 mock。

supertest 是可以用來對介面進行黑盒測試的模塊之一,下面是基於測試框架 mocha 編寫的一個測試用例,該用例的目的是檢查介面是否能返回單條的用戶數據:

const request = require(supertest)nndescribe(GET /user/:id, function() {n it(returns a user, function() {n // newer mocha versions accepts promises as welln return request(app)n .get(/user)n .set(Accept, application/json)n .expect(200, {n id: 1,n name: John Mathn }, done);n });n});n

可能有人會問: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);nconst jwt = require(koa-jwt);nnconst app = koa();nnapp.use(jwt(n secret: very-secretn}));nn// Protected middlewarenapp.use(function*() n // content of the token will be available on this.state.usern this.body = { secret: 42 }n});n

有了如上的代碼,你的 API 就有了 JWT 的保護。如果要訪問這種被保護的介面,需要使用 Authorization Header 來提供 token,比如:

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

你可能注意到了,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 文檔:

  • API Blueprint
  • Swagger

如果你願意使用第三方文檔服務商,可以考慮 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:

  • GitHub API
  • Twilio API
  • Stripe API
  • Digital Ocean API

希望讀到這裡的同學對如何用 Node.js 編寫良好的 API 有更好的理解,如果有建議,歡迎評論中提出。

One More Thing

想讀到更多類似內容?請訂閱我的專欄《前端周刊,讓你在前端領域跟上時代的腳步》,或者掃描本文封面中的二維碼訂閱微信號。

Happy Hacking


推薦閱讀:

express4.x Request對象獲得參數方法小談
4.2 目錄結構-博客後端Api-NodeJs+Express+Mysql實戰
Node.js中request+response數據結構分解
Node.js 性能調優之CPU篇(二)——v8-profiler
酷站推薦 - mermaidjs.github.io - diagrams and flowcharts from markdown text

TAG:Nodejs | 前端框架 | 前端工程师 |