Express 實戰(六):構建 API 接口

Cover
Cover

在介紹了那麼多 Express 核心概念以後,接下來的文章將會把注意力放在如何構建一個真實的應用上。這裏咱們先從構建應用 API 接口開始。從某種程度上來講幾乎全部的軟件應用其背後都是由一組強大的 API 驅動。html

其實 API 就是一種代碼之間交互的一種方式,它既能夠是在程序內部也能夠是經過網絡的跨機器進行。例如,Express 中的 app.useapp.get 就屬於在內部使用 API 。而經過 HTTP 或者 FTP 等協議發送 JSON、XML 數據的方式則屬於後者。對於後一種方式須要注意的是,API 的提供者和使用者必須對數據格式作出約定。在本文示例中,咱們將會討論如何使用 Express 構建後一類型的 API 接口,同時全部 HTTP 接口返回的數據格式都將使用 JSON。node

另外,本章還會討論如何設計一個優雅的 API 用於提高使用者的體驗和效率,讓 API 的含義一目瞭然而不用去閱讀又臭又長的說明文檔。就像「好代碼」與「壞代碼」同樣,API 是否優雅其實更多的取決於實際情形。盲目遵循 API 設計的最佳實踐有時會顯得很迂腐,由於它有可能與使用者的指望不一致。web

接下來的內容包括:數據庫

  • 什麼是 API 。
  • Express 中構建 API 的基礎內容。
  • HTTP 方法與應用邏輯的關聯。
  • 多版本 API 的實現和管理。
  • HTTP 狀態碼的正確使用。

簡單的 JSON 格式 API 示例

首先,咱們須要明確該示例的功能以及 API 的使用方式,後面再寫代碼。express

假設,如今程序須要在接受到 America/Los_AngelesEurope/London 等表明時區的字符串後,返回該時區的當前時間信息(例如:2015-04-07T20:09:58-07:00 )。該返回信息與現實中易懂的時間格式是不同的,由於它是爲計算機設計的。json

經過相似下面格式的 URL 的 HTTP 請求來調用應用 API:api

/timezone?tz=America+Los_Angeles複製代碼

而服務端 API 返回的 JSON 的數據格式,以下:瀏覽器

{
    "time": "2015-06-09T16:20:00+01:00",
    "zone": "America/Los_Angeles"
}複製代碼

只要能調用 API 並對 JSON 數據進行解析,你就能夠在任意平臺構建任意應用程序。以下圖,你能夠經過 AJAX 請求該 API 實現一個展現時區信息的單頁應用。bash

06_01
06_01

你也能夠利用該接口實現下圖所示的移動應用。服務器

06_02
06_02

你甚至能夠利用該 API 實現下圖同樣的終端命令行工具:在終端中打印服務端 API 接口返回的數據。

06_03
06_03

像前一章的天氣應用同樣,咱們能夠利用這些 API 返回的冰冷數據構建更具表達力的 UI 。

Express 驅動的 JSON API 服務

瞭解 API 概念以後,下面咱們就動手實現一個 Express 驅動的 API 服務。實現的原理很是簡單:經過中間件和內置函數解析網絡請求並將 JSON 數據和 HTTP 狀態碼封裝到響應對象並返回給客戶端。

從技術角度上說,API 服務除了使用 JSON 格式外,你還能夠是使用 XML 或者純文本。可是 Express 和 JavaScript 對 JSON 的支持是最好的,同時它也是當前最流行的格式,因此後面會一直使用 JSON 做爲默認數據格式。

下面咱們編寫一個爲多平臺提供隨機數生成的服務,該 API 將擁有以下特性:

  • 在請求 API 時必須附帶隨機數最小值和最大值。
  • 解析請求獲取隨機數範圍並將生產的結果以 JSON 格式返回。

你可能認爲這裏徹底可使用純文原本替換 JSON 格式。可是發送 JSON 數據是開發者的必備技能,並且 JSON 格式極易拓展。

該工程的構建步驟以下:

  1. 新建 package.json
  2. 建立工程主入口文件 app.js
  3. app.js 中建立應用和路由中間件。

首先,在新建的 package.json 中,複製下面的內容並按照依賴項:

{
    "name": "random-number-api",
    "private": true,
    "scripts": {
        "start": "node app"
    },
    "dependencies": {
        "express": "^5.0.0" 
    }
}複製代碼

接下來,將下面的代碼複製到入口文件 app.js 中:

var express = require("express");
var app = express();

app.get("/random/:min/:max", function(req, res) {
    var min = parseInt(req.params.min);
    var max = parseInt(req.params.max);
    if (isNaN(min) || isNaN(max)) {
        res.status(400);
        res.json({ error: "Bad request." });
        return;
    }

    var result = Math.round((Math.random() * (max - min)) + min);
    res.json({ result: result });
});

app.listen(3000, function() {
    console.log("App started on port 3000");
});複製代碼

如今啓動應用並訪問 http://localhost:3000/random/10/100 的話,你將看到一個附帶 10 ~ 100 範圍內隨機數的 JSON 數據。

06_04
06_04

接下來,咱們來分析上面的代碼。

與以前同樣,前兩行代碼引入了 Express 並建立了一個 Express 應用實例。

而後,咱們建立了一個路由中間件用於處理相似 /random/10/100 這樣的 API 請求。固然,這裏還存在一些 bug ,例如,沒有過濾掉 /random/foo/bar 請求。因此,在調用 API 的時候請確保使用的參數是整型變量。

在而後,咱們使用內置的 parseInt 解析範圍參數,而該函數的返回值只多是整形數字或者 NaN。若是傳入的參數有一個爲 NaN 的話就會給客戶端返回一個錯誤信息。下面這部分代碼對於整個程序來講是很是重要的:

if (isNaN(min) || isNaN(max)) {
  res.status(400);
  res.json({ error: "Bad request." });
  return;
}複製代碼

若是上面的參數檢查的結果是最少有一個爲 NaN ,程序就會進行以下處理:

  1. 設置 HTTP 狀態碼爲 400。常見的 404 錯誤就是它的一個具體變種,表示的含義是:用戶請求的出現了問題。
  2. 發送包含錯誤信息的 JSON 數據。
  3. 結束請求處理並跳出中間件執行。

在代碼的最後,咱們會在合法的參數返回內生成隨機數並將結果返回給客戶端。

雖然示例很簡單,可是它已經包含了使用 Express 構建 API 的基本流程:解析請求,設置 HTTP 狀態碼,返回響應數據。你能夠在這個基礎之上構建更爲複雜優雅的 API 。

CURD 操做 API

CURD 是對程序中 Create、Read、Update、Delete 四種業務動做的一個簡稱。

大多數的應用都會涉及到 CURD 操做。例如,對於一個圖片分享應用來講,其中涉及圖片的全部操做就是典型的 CRUD:

  • 用戶上傳照片的行爲對應就是 create 操做。
  • 用戶瀏覽照片的行爲就是 read 操做。
  • 用戶更新照片的行爲就是 update 操做。
  • 用戶刪除照片的行爲就是 delete 操做。

不管是分享照片的社交應用仍是文件存儲服務,你生活中的使用的不少服務中都使用了這種模式。不過在開始討論構建 CRUD 功能的 API 以前,咱們先來看看被稱爲 HTTP 方法的內容。

HTTP 方法

HTTP 的規範中是這樣定義其方法的:

HTTP 方法明確了對請求 URI 所標識資源進行的操做,並且方法是區分大小寫的。

一個更易理解的解釋是:客戶端在發送 HTTP 請求時須要指定一個 HTTP 方法,而後服務端回依據不一樣的 HTTP 方法作出不一樣的響應。雖然,可用的 HTTP 方法有不少,可是經常使用的其實並很少。其中在 Web 應用中經常使用是下面 4 個:

  1. GET 是最經常使用的一個 HTTP 方法,它表示請求服務端資源。例如,加載網站首頁、請求圖片資源都使用的是 GET。雖然服務端的響應可能不一樣,可是GET 請求並不會改變服務器的資源。例如,對某圖片資源的一次或者屢次請求並不會致使圖片自己出現任何差異。
  2. POST 是另外一個經常使用的 HTTP 方法。例如,建立新博客、上傳照片、註冊用戶、清空購物車等業務都是使用 POST 。與 GET 不一樣的是:每次 POST 請求都會致使服務端發生修改。
  3. PUT 方法用於對已有記錄的修改,全部我以爲它應該被稱爲 "UPDATE" 更爲合適。例如,修改博客標題、修改用戶暱稱等操做都是 PUT 操做。另外,PUT 還具有 POST 的功能:就是當要修改的記錄不存在時能夠進行新建操做(非必需)。其次 PUT 還具備 GET 方法的特色:對同一 URL 的一次或屢次 PUT 請求後的結果是一致的。
  4. DELETE 方法用於記錄刪除。例如,刪除用戶文章、刪除網絡照片。另外,與 PUT 同樣同一刪除請求不管是執行一次仍是屢次最終結果是一致的。

雖然 HTTP 還有不少其餘的方法,可是它們在現實開發過程當中並不常見。理論上你甚至能夠只使用 GET 和 POST 請求完成全部業務,可是這是錯誤實踐畢竟它違反了 HTTP 規範也會給開發者形成困惑。另外,不少瀏覽器也是根據 HTTP 方法來明確所執行的操做類型。因此,即便並無強制你也應該參照該規範來約束本身的行爲。

前面你已經見過 Express 中對部分方法的處理,不過下面的代碼將一次涵蓋上面全部的四個方法:

var express = express("express");
var app = express();

app.get("/", function(req, res) {
  res.send("you just sent a GET request, friend");
});

app.post("/", function(req, res) {
  res.send("a POST request? nice");
});

app.put("/", function(req, res) {
  res.send("i don't see a lot of PUT requests anymore");
});

app.delete("/", function(req, res) {
  res.send("oh my, a DELETE??");
});

app.listen(3000, function() {
  console.log("App is listening on port 3000");
});複製代碼

將代碼複製到入口文件 app.js 中並啓動服務,而後你就可使用 cURL 命令測試不一樣的 HTTP 方法了。默認狀況下 cURL 使用 GET 發送請求,可是你可使用 -X 選項來指定其餘的方法。例如,curl -X PUT http://localhost:3000

06_05
06_05

經過 HTTP 方法構建 CRUD 接口

回想如下以前的照片分享應用,下面是其中可能的 CRUD 操做:

  1. 用戶上傳圖片,此爲 Create
  2. 用戶瀏覽圖片,此爲 Read
  3. 用戶更新圖片備註等信息,此爲 Update
  4. 用戶從站點刪除圖片,此爲 Delete

不難看出 CRUD 操做與以前四種 HTTP 方法存在對應關係:

  • Create = POST
  • Read = GET
  • Update = PUT
  • Delete = DELETE

所以經過這四個 HTTP 方法咱們能夠很好的實現最多見 CRUD 風格的 web 應用程序。

實際上對於更新和建立動做與 HTTP 方法的對應關係,一些人有着本身的見解。它們認爲 PUT 更應該對應建立動做而非 POST。另外,新的 PATCH 方法則對應更新操做。雖然本文將會使用上面那種更規範的對應關係,可是你徹底能夠按照本身的意願選擇。

API 版本控制

爲了應對將來可能的 API 更新,對 API 進行版本控制是一件很是高效的方法。例如,前面獲取指定時區當前時間的 API 在推出後就被不少的廠商和開發者使用。可是,幾年幾後因爲某些緣由必須對該 API 進行更新而與此同時你又不能影響以前的使用者。此時,咱們就能夠經過添加新版原本解決這個問題。其中原有的 API 請求能夠經過:

/v1/timezone

而新版本 API 請求則可使用:

/v2/timezone

這樣不只在進行 API 更新時防止了代碼的破壞性更改。並且接口使用者也有了更靈活的選擇,他們能夠在必要的時候進行 API 切換。

在 Express 中可使用 Router 中間件來實現 API 版本管理。拷貝下面代碼到文件 app1.js 中,並講其做爲第一個版本 API 的實現:

var express = require("express");
var api = express.Router();

api.get("/timezone", function(req, res) {
    res.send("Sample response for /timezone");
});
api.get("/all_timezones", function(req, res) {
    res.send("Sample response for /all_timezones");
});

module.exports = api;複製代碼

請注意,上面的中間件代碼在處理的 URL 並無包含 /v1 。下面在入口文件中引入這個 Router 中間件並進行路由映射。

var express = require("express");
var apiVersion1 = require("./api1.js");
var app = express();
app.use("/v1", apiVersion1);
app.listen(3000, function() {
    console.log("App started on port 3000");
});複製代碼

而後,你將最新版本的 API 實現放在 api2.js 文件中:

var express = require("express");
var api = express.Router();

api.get("/timezone", function(req, res) {
    res.send("API 2: super cool new response for /timezone");
});
module.exports = api;複製代碼

最後,經過 Router 將這兩個版本的 API 同時添加到主入口中:

var express = require("express");

var apiVersion1 = require("./api1.js");
var apiVersion2 = require("./api2.js");
var app = express();

app.use("/v1", apiVersion1);
app.use("/v2", apiVersion2);
app.listen(3000, function() {
    console.log("App started on port 3000");
});複製代碼

你能夠經過瀏覽器驗證這些版本化後的 API 是否正確工做,另外你也可使用 cURL 命令進行測試。

06_07
06_07

就像前面章節介紹的那樣,Router 可讓你將不一樣的路由存放在不一樣文件中進行管理。而版本化 API 就是最典型的應用實例。

設置 HTTP 狀態碼

每個 HTTP 響應都應該附帶一個 HTTP 狀態碼,其中最有名的就是 404 Not Found

雖然 404 是最出名的,可是 200 狀態碼確是最多見的。與 404 不一樣的是,雖然當網頁成功加載或 JSON 數據成功返回後都會包含狀態碼 200,但它並不會被展現出來。

固然,除了 404 和 200 以外,HTTP 中還定義了不少其餘的狀態碼,包括 100、200、300、400 以及 500 系列。須要注意的是並非每一個系列中全部 100 個數字都有明肯定義,例如,100 系列只有 100,101,102 三個有效碼,緊跟其後就是 200 。

每一個狀態碼系列其實都有特定的含義和主題,總結就是:

1xx: 成功接收到請求。
2xx: 成功
3xx: 重定向
4xx: 客戶端錯誤
5xx: 服務端錯誤

規範中只定義的大約 60 個狀態碼。你能夠在此基礎上拓展本身的狀態碼,可是一般並不會這麼作。由於優秀的 API 的首要設計原則就是確保不會對使用者形成任何歧義,因此應該最大程度遵循官方規範的指導。後面咱們會對上面的每一個區間的狀態碼進行講解,可是在此以前先來看看如何在 Express 中設置狀態碼。

少部分應用還在使用 HTTP 1.0 版本的協議,而大部分以及切換到了 1.1 版本。做爲下一個版本的 HTTP 2.0 標準如今也逐漸在推廣過程當中。幸運的是,2.0 版本的協議大部分更新都在底層因此切換時並不會涉及太大的工做量。另外,2.0 版本還新增了一個 421 的狀態碼。

設置 HTTP 狀態碼

默認狀況下,HTTP 狀態碼是 200。若是用戶訪問的 URL 對應資源不存在的話,Express 會發送 404 錯誤。若是訪問的服務器出現問題的話,Express 就會發送 500 錯誤。

可是這些都是 Express 的默認行爲,某些情形下可能會須要自行設置狀態碼。爲此,Express 的 response 對象提供了一個 status 方法,你須要在調用是傳入對應狀態碼就能完成設置。

// ...
res.status(404);
// ...複製代碼

該方法能夠進行鏈式調用,因此你能夠緊跟其後使用 json 設置返回的數據。

res.status(404).json({ error: "Resource not found!" });
// 它等價於:
res.status(404);
res.json({ error: "Resource not found!" });複製代碼

雖然 Express 對原生 Node 的 response 對象進行了拓展,而且在使用 Express 時也應遵循 Express 風格,可是你依舊可使用原生方法來完成設置。

res.statusCode = 404;複製代碼

100 區間

100 區間的官方狀態碼只有兩個:100(繼續) 和 101 (切換協議),並且它們不多會被用到。若是你必須處理的話,能夠去官網或者維基上查看。

200 區間

200 區間狀態碼錶示請求成功。雖然該區間狀態碼很多,可是經常使用的也就下面 4 個:

  • 200:做爲最多見的狀態碼,它也被稱爲 "OK"。這意味着請求和響應都正確執行期間並無出現任何錯誤或者重定向操做。
  • 201:與 200 十分相似,可是使用情形略有不一樣。它一般用於 POST 或者 PUT 請求成功建立記錄後。例如,建立博文、上傳圖片等操做成功後就會發送 201。
  • 202:202 是 201 的一個變種。由於,資源的建立大可能是異步進行的,而這些操做也是費時的。因此,你能夠在此時給客戶端響應 202 。它表示已經成功接收數據正在等待建立。
  • 204:它表示用戶刪除請求所對應的資源並不存在已經被刪除過了。

300區間

一樣,在 300 區間,咱們只介紹其中經常使用的三個,而且它們全都涉及重定向。

  • 301:它表示所訪問資源位置已經發生修改,請訪問最新的 URL 。一般它還會附帶一個 Location 的頭部信息指明重定向的位置。
  • 303:它表示請求的資源已經建立完成,如今你就會被重定位到一個新頁面。
  • 307:與 301 相似都是提示當前 URL 不存在。不過區別是,301 的重定向是永久的而 307 可能重定向的只是一個臨時性 URL 。

400 區間

400 區間的狀態碼是最多的,而它一般都是表示因爲客戶端的錯誤致使請求失敗。

  • 401 和 403:這兩個狀態碼分別表示「未受權」和「禁止」。字面上看二者很相似,可是前者可能表示用戶未登陸然後者則多是用戶登陸了可是權限不夠。
  • 404:它表示用戶 URL 請求的資源並不存在。

至於該區間其餘狀態碼,讀者能夠去維基上自行查看,這裏就不一一介紹了。另外,當你不肯定應該使用哪一種客戶端錯誤狀態碼時,你能夠直接使用 400 。

500 區間

做爲 HTTP 規範裏的最後一個區間,500 區間狀態碼錶示的是服務內部出現錯誤。例如,請求過載或者數據庫鏈接中斷。另外,理論上該區間的錯誤只能有服務內部本身觸發。最後,爲了防止黑客窺探太多內部信息,你能夠對全部的內部錯誤僅僅返回一個抽象的「內部服務器錯誤」這樣的信息。

總結

本章包含的內容有:

  • 使用 Express 構建 API 服務。
  • HTTP 方法以及與 CRUD 操做之間的關係。
  • 若是對 API 進行版本控制,提示服務的兼容性和穩定性。
  • HTTP 狀態碼的使用和其意義。

原文地址

相關文章
相關標籤/搜索