Express 教程第 6 部分:使用表單
在本教程中,我們將向您展示如何使用 Pug 在 Express 中處理 HTML 表單。特別是,我們將討論如何編寫表單來從網站資料庫中建立、更新和刪除文件。
| 預備知識 | 完成所有之前的教程主題,包括Express 教程第 5 部分:顯示圖書館資料 |
|---|---|
| 目標 | 瞭解如何編寫表單以從使用者獲取資料,並使用此資料更新資料庫。 |
概述
HTML 表單是網頁上一個或多個欄位/小部件的集合,可用於從使用者那裡收集資訊以提交到伺服器。表單是收集使用者輸入的靈活機制,因為有適合輸入多種不同型別資料(文字框、複選框、單選按鈕、日期選擇器等)的表單輸入。表單也是一種與伺服器共享資料的相對安全的方式,因為它們允許我們使用跨站請求偽造保護在 POST 請求中傳送資料。
使用表單可能很複雜!開發人員需要編寫表單的 HTML,在伺服器(可能也在瀏覽器)上驗證和正確清理輸入的資料,用錯誤訊息重新發布表單以通知使用者任何無效欄位,在成功提交資料後處理資料,最後以某種方式響應使用者以指示成功。
在本教程中,我們將向您展示如何在 Express 中執行上述操作。在此過程中,我們將擴充套件 LocalLibrary 網站,允許使用者從圖書館建立、編輯和刪除專案。
注意:我們尚未研究如何限制特定路由給經過身份驗證或授權的使用者,因此此時,任何使用者都將能夠對資料庫進行更改。
HTML 表單
首先簡要概述HTML 表單。考慮一個簡單的 HTML 表單,其中包含一個用於輸入某個“團隊”名稱的文字欄位及其關聯的標籤。
![]()
表單在 HTML 中定義為 <form>…</form> 標籤內的一組元素,其中至少包含一個 type="submit" 的 input 元素。
<form action="/team_name_url/" method="post">
<label for="team_name">Enter name: </label>
<input
id="team_name"
type="text"
name="name_field"
value="Default name for team." />
<input type="submit" value="OK" />
</form>
雖然這裡只包含一個(文字)欄位用於輸入團隊名稱,但表單*可以*包含任意數量的其他輸入元素及其關聯的標籤。欄位的 type 屬性定義將顯示哪種小部件。欄位的 name 和 id 用於在 JavaScript/CSS/HTML 中標識該欄位,而 value 定義了該欄位首次顯示時的初始值。匹配的團隊標籤使用 label 標籤指定(參見上面的“輸入名稱”),其中 for 欄位包含關聯 input 的 id 值。
submit 輸入將顯示為一個按鈕(預設情況下)——使用者可以按下此按鈕將其他輸入元素(在本例中,只是 team_name)包含的資料上傳到伺服器。表單屬性定義用於傳送資料的 HTTP method 和伺服器上資料的目的地(action)。
action:當表單提交時,資料將傳送到此資源/URL 進行處理。如果未設定(或設定為空字串),則表單將提交回當前頁面 URL。method:用於傳送資料的 HTTP 方法:POST或GET。- 如果資料將導致伺服器資料庫發生更改,則應始終使用
POST方法,因為這樣可以更好地抵抗跨站偽造請求攻擊。 GET方法只應用於不更改使用者資料的表單(例如,搜尋表單)。建議在您希望能夠書籤或共享 URL 時使用它。
- 如果資料將導致伺服器資料庫發生更改,則應始終使用
表單處理過程
表單處理使用了我們學習顯示模型資訊的所有相同技術:路由將我們的請求傳送到控制器函式,該函式執行所有必需的資料庫操作,包括從模型中讀取資料,然後生成並返回 HTML 頁面。使事情更復雜的是,伺服器還需要能夠處理使用者提供的資料,並在出現任何問題時重新顯示帶有錯誤資訊的表單。
下面顯示了處理表單請求的過程流程圖,從請求包含表單的頁面(顯示為綠色)開始。

如上圖所示,表單處理程式碼主要需要做的事情是:
-
使用者首次請求時顯示預設表單。
- 表單可能包含空白欄位(例如,如果您正在建立新記錄),或者可能預填充了初始值(例如,如果您正在更改記錄,或者具有有用的預設初始值)。
-
接收使用者提交的資料,通常透過 HTTP
POST請求。 -
驗證並清理資料。
-
如果任何資料無效,重新顯示錶單——這次顯示使用者填寫的值和問題欄位的錯誤訊息。
-
如果所有資料都有效,執行所需操作(例如,將資料儲存到資料庫中,傳送通知電子郵件,返回搜尋結果,上傳檔案等)。
-
所有操作完成後,將使用者重定向到另一個頁面。
通常,表單處理程式碼使用 GET 路由用於表單的初始顯示,以及指向相同路徑的 POST 路由用於處理表單資料的驗證和處理。這就是本教程將使用的方法。
Express 本身不提供任何特定的表單處理操作支援,但它可以使用中介軟體來處理表單中的 POST 和 GET 引數,並驗證/清理它們的值。
驗證與清理
在儲存表單資料之前,必須對其進行驗證和清理。
- 驗證檢查輸入的值是否適合每個欄位(是否在正確的範圍、格式等),並且所有必填欄位都已提供值。
- 清理刪除/替換資料中可能用於向伺服器傳送惡意內容的字元。
在本教程中,我們將使用流行的 express-validator 模組來對我們的表單資料執行驗證和清理。
安裝
透過在專案根目錄中執行以下命令來安裝模組。
npm install express-validator
使用 express-validator
注意: GitHub 上的 express-validator 指南提供了 API 的良好概述。我們建議您閱讀該指南以瞭解其所有功能(包括使用 schema validation 和 建立自定義驗證器)。下面我們僅介紹對 LocalLibrary 有用的一部分。
要在控制器中使用驗證器,我們需要從 express-validator 模組中匯入我們想要使用的特定函式,如下所示:
const { body, validationResult } = require("express-validator");
有許多可用的函式,允許您檢查和清理來自請求引數、正文、標頭、cookie 等的資料,或一次性處理所有這些資料。在本教程中,我們將主要使用 body 和 validationResult(如上所示為“必需”)。
這些函式定義如下:
-
body(fields, message):指定請求正文中的一組欄位(POST引數)以進行驗證和/或清理,以及一個可選的錯誤訊息,如果測試失敗,則可以顯示該訊息。驗證和清理條件會鏈式地附加到body()方法。例如,下面這行程式碼首先定義我們要檢查“name”欄位,如果驗證失敗,將設定錯誤訊息“Empty name”。然後我們呼叫清理方法
trim()來刪除字串開頭和結尾的空格,然後呼叫isLength()來檢查結果字串是否為空。最後,我們呼叫escape()來刪除可能在 JavaScript 跨站指令碼攻擊中使用的 HTML 字元。js[ // … body("name", "Empty name").trim().isLength({ min: 1 }).escape(), // … ];此測試檢查年齡欄位是否為有效日期,並使用
optional()指定 null 和空字串不會導致驗證失敗。js[ // … body("age", "Invalid age") .optional({ values: "falsy" }) .isISO8601() .toDate(), // … ];您還可以鏈式使用不同的驗證器,並新增在之前的驗證器為 false 時顯示的訊息。
js[ // … body("name") .trim() .isLength({ min: 1 }) .withMessage("Name empty.") .isAlpha() .withMessage("Name must be alphabet letters."), // … ]; -
validationResult(req):執行驗證,以validation結果物件的形式提供錯誤。這在單獨的回撥中呼叫,如下所示:jsasync (req, res, next) => { // Extract the validation errors from a request. const errors = validationResult(req); if (!errors.isEmpty()) { // There are errors. Render form again with sanitized values/errors messages. // Error messages can be returned in an array using `errors.array()`. } else { // Data from form is valid. } };我們使用驗證結果的
isEmpty()方法來檢查是否存在錯誤,並使用其array()方法來獲取錯誤訊息集。有關更多資訊,請參閱處理驗證部分。
驗證和清理鏈是應該傳遞給 Express 路由處理程式的中介軟體(我們透過控制器間接執行此操作)。當中間件執行時,每個驗證器/清理器都按指定的順序執行。
當我們在下面實現 LocalLibrary 表單時,我們將介紹一些真實的例子。
表單設計
庫中的許多模型是相關/依賴的——例如,Book 需要一個 Author,並且可能還具有一個或多個 Genres。這就提出了一個問題,即我們應該如何處理使用者希望進行以下操作的情況:
- 在相關物件尚不存在時建立物件(例如,作者物件尚未定義的書籍)。
- 刪除仍被其他物件使用的物件(例如,刪除仍被
Book使用的Genre)。
對於本專案,我們將透過宣告一個表單只能執行以下操作來簡化實現:
- 使用已存在的物件建立物件(因此使用者必須在嘗試建立任何
Book物件之前建立任何必需的Author和Genre例項)。 - 如果一個物件沒有被其他物件引用,則刪除該物件(例如,在所有關聯的
BookInstance物件被刪除之前,您將無法刪除Book)。
注意:更靈活的實現可能允許您在建立新物件時建立依賴物件,並隨時刪除任何物件(例如,透過刪除依賴物件,或從資料庫中刪除對已刪除物件的引用)。
路由
為了實現我們的表單處理程式碼,我們將需要兩個具有相同 URL 模式的路由。第一個(GET)路由用於顯示一個新的空表單以建立物件。第二個(POST)路由用於驗證使用者輸入的資料,然後儲存資訊並重定向到詳細資訊頁面(如果資料有效)或重新顯示帶有錯誤的表單(如果資料無效)。
我們已經在 /routes/catalog.js 中為所有模型的建立頁面建立了路由(在之前的教程中)。例如,流派路由如下所示:
// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get("/genre/create", genre_controller.genre_create_get);
// POST request for creating Genre.
router.post("/genre/create", genre_controller.genre_create_post);
Express 表單子文章
以下子文章將引導我們完成向示例應用程式新增所需表單的過程。您需要逐一閱讀並完成每個表單,然後才能進入下一個表單。
挑戰自我
實現 Book、BookInstance 和 Genre 模型的刪除頁面,並以與我們的 *Author 刪除*頁面相同的方式從關聯的詳細資訊頁面連結它們。這些頁面應遵循相同的設計方法:
- 如果物件被其他物件引用,則應顯示這些其他物件以及一條註釋,說明在刪除列出的物件之前無法刪除此記錄。
- 如果沒有其他物件引用該物件,則檢視應提示刪除它。如果使用者按下 **Delete** 按鈕,則應刪除該記錄。
一些小貼士
- 刪除一個
Genre就像刪除一個Author,因為這兩個物件都是Book的依賴項(因此在這兩種情況下,只有當關聯的書籍被刪除時,您才能刪除該物件)。 - 刪除
Book也很類似,您需要先檢查沒有關聯的BookInstances。 - 刪除
BookInstance是所有操作中最簡單的,因為沒有依賴物件。在這種情況下,您只需找到相關的記錄並將其刪除。
實現 BookInstance、Author 和 Genre 模型的更新頁面,並以與我們的 Book 更新頁面相同的方式從關聯的詳細資訊頁面連結它們。
一些小貼士
- 我們剛剛實現的 圖書更新頁面 是最難的!相同的模式可用於其他物件的更新頁面。
Author的死亡日期和出生日期欄位以及BookInstance的到期日期欄位的格式不適合在表單的日期輸入欄位中輸入(它需要“YYYY-MM-DD”格式的資料)。解決此問題的最簡單方法是為日期定義一個新的虛擬屬性,該屬性以適當的格式格式化日期,然後在關聯的檢視模板中使用此欄位。- 如果您遇到困難,可以在此處的示例中找到更新頁面的示例。
總結
Express、Node 和 npm 上的第三方包提供了您將表單新增到網站所需的一切。在本文中,您學習瞭如何使用 Pug 建立表單,使用 express-validator 驗證和清理輸入,以及在資料庫中新增、刪除和修改記錄。
現在您應該瞭解如何在自己的 Node 網站中新增基本表單和表單處理程式碼了!
另見
- express-validator (npm 文件)。