[筆記] 把玩 express-validator 在伺服器端做表單驗證
在許多網路應用程式中,經常會透過表單獲得使用者輸入,當使用者要送出表單時,工程師的考驗就來了:「我該如何驗證表單內的資訊?」、「驗證失敗時,如何提供好的使用者體驗?」 — 今天要把玩的是後端驗證模組「express-validator」
簡易資料驗證流程
前端驗證
簡單的前端驗證,可以透過使用 HTML 和使用 JavaScript 監聽事件、驗證、操作 DOM 等來達成,甚至能適時提示錯誤訊息和保留使用者前一次輸入的內容,來提升使用者體驗。
然而,我們寫的 JavaScript 是運行在前端(瀏覽器當中)使用者不僅能看到這些 JavaScript 程式碼,還有機會做修改,甚至透過瀏覽器,關閉 JavaScript 的設定 — 也就是說,這些來自前端的表單資訊對於後端伺服器來說,可能不是正確或安全的 — 增加後端伺服器驗證會是更理想的!
後端伺服器驗證
後端伺服器的驗證相對安全 — 使用者看不到也無法修改這些後端的程式碼 — 因此透過後端伺服器驗證,我們能確保在運用這些使用者輸入,或要將這些資訊存入資料庫以前,這些資訊都已經正確,且符合理想的格式。
express-validator
根據官方的文件說明:
express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.
簡單來說:express-validator
包含了許多能在 Express 當中使用的middlewares — 這些 middlewares 包含了驗證和 Sanitizer 的功能。
說到驗證,餐廳清單的會員註冊功能就是其中需要使用到驗證的地方,以下會以驗證註冊表單做為出發點做舉例:
安裝 express-validator 套件
$ npm install express-validator
引入 express-validator 套件
const { check } = require('express-validator')
首先要先引入 express-validator 模組中在處理驗證的 middlewares — 其中包含了check()
、body()
、cookie()
等函式可以使用 — 這邊引入了check
函式:
- check([field, message])
Field
:要驗證的欄位名稱(格式:字串或包含許多字串的陣列)
<!--此範例中要驗證的欄位名稱為 email-->
<input type="email" name="email">
Message
:驗證失敗時回傳的錯誤訊息(預設:Invalid value)- 回傳值:一個「驗證鏈(Validation Chain)middlewares」 — 在這個鏈上,可以開始串上許多驗證和 Sanitizer 的功能。
❗️問題 1:check() 究竟會到哪去尋找這個名稱的欄位呢?📣答案 1:從 request 中的六個物件裡尋找:
req.body、req.cookies、req.headers、req.param、req.query❗️問題 2:如果在不只一個地方找到這個名稱的欄位會怎麼處理?📣答案 2:則所有找到的欄位都必須通過驗證
2. check() 以外的其他函式
其他如body()
或cookie()
等函式和check()
函式的用途幾乎一樣,差異在於尋找欄位的地方。例如:body()
只會從req.body
這個請求裡的物件尋找;cookie()
只會從req.cookies
這個請求裡的物件尋找。(詳細參考這篇)
使用 express-validator:在 Express 路徑上加入驗證鏈 middlewares
- 驗證鏈上串接的驗證和 Sanitizer 功能
由於 express-validator 是依賴 validator.js,因此有許多內建的驗證和 Sanitizer 功能可以直接使用 — 前面有提到,check()
或body()
等函式回傳的是一個「驗證鏈 middleware」在鏈上就可以開始串上這些驗證和 Sanitizer 的功能了。詳細清單可以查看「validator.js 官方文件」和「express-validator 官方文件」:
- Validators:確保資料符合驗證條件
- Sanitizers:確保資料符合儲存的格式
❗️問題:驗證鏈上的驗證和 Sanitizer 功能有執行的先後順序嗎?📣答案:有的,會依照功能撰寫的順序執行。因此可以透過 Sanitizer 將資料先轉換成期
待的格式再來驗證;也可以先驗證,再將資料透過 Sanitizer 做格式上的轉換
2. 在 Express 路徑上加入驗證鏈 middlewares
透過check()
尋找名為 email 的欄位,並確認該欄位的輸入符合 Email 格式
isEmail()
驗證方法可以從「validator.js 官方文件」中找到詳細資料:
當然也可以不只針對 Email 一個欄位做驗證,譬如我們也想針對密碼欄位做驗證 — 密碼長度至少要五碼— 此時,我們可以將「多個驗證鏈 middlewares 」透過陣列包裝起來 — 這對程式碼的可讀性和可維護性都有所提升:
3. 在驗證鏈上加入客製化的驗證功能
除了使用文件上能找到的內建驗證和 Sanitizer 功能 — 如isEmail()
或isLength()
— 也可以透過custom(validator)
自訂方法來使用 — 以下將客製化一個驗證方法,來確認註冊時所輸入的密碼和確認密碼需要相同:
validator(value, { req, location, path }):客製化驗證函式
value
:目前在驗證的欄位中的值{req, location, path}
:「選擇性」的帶入該路徑的請求、位置、欄位的路徑來使用
❗️問題:「確認密碼」不也該符合長度嗎?不需要在驗證「確認密碼」時加入該驗證嗎?📣答案:「確認密碼」的長度其實也必須要為五碼以上,但我們已經在驗證「密碼」欄位時
確認密碼長度符合要求,因此只要「確認密碼」的值和「密碼」欄位的值相同,就
也能同時能達到要求了
使用 express-validator:獲取驗證錯誤訊息來呈現
前面的範例中,我們在驗證鏈 middlewares 上針對不同欄位做了驗證,而驗證失敗時,也設定了相對應的錯誤訊息。接下來就要在同一條路徑上的 authController
中透過 validationResult(req)
從請求中將錯誤訊息取出 — 取出的內容將統一被放在一個Result
的物件當中回傳:
validationResult(req)
req
:從該路徑的請求中獲取一個或多個錯誤訊息- 回傳:一個有包含錯誤訊息的
Result
物件(如以下範例)
在 Terminal 中將存放Result
物件的errors
印出來看看:
在 authController 中使用錯誤訊息
const { name, email, password, rePassword } = req.body
:將使用者在註冊時所輸入的資料保留起來(第七點將會使用)
(在這使用了解構賦值,如果不懂可以看👉這篇)const errors = validationResult(req)
:將回傳的錯誤訊息存在errors
errors.isEmpty()
:判定是否沒有錯誤訊息(加上!
則相反)res.status(422)
:有錯誤訊息時,回傳 HTTP 狀態碼 422 表示驗證失敗res.render(‘user’)
:有錯誤訊息時,重新渲染註冊頁面的模板errors.array()
:包含所有錯誤訊息的陣列 — 在渲染註冊頁面時,可以一一將錯誤訊息顯示出來user: {name, email, password, repassword}
:在重新渲染註冊頁面時,將使用者原本輸入的資料保留在欄位中,以增加使用者體驗
錯誤訊息顯示
最後一步 — 於表單上顯示錯誤訊息。在 express-handlebars 模板中簡單透過使用 Bootstrap 的 Alerts 將錯誤訊息顯示出來,針對表單中錯誤訊息的程式碼和表單結果範例如下:
結語
首先…太好玩了!
再者,藉由使用 express-validator 套件,我們得以在表單經過前端驗證後(非必要,但可以增加使用者體驗)於伺服器端再次做驗證,這麼做有幾個好處:
- 由於使用者看不到也無法變更後端程式碼,因此較安全
- 透過 validators 驗證功能得以再次驗證使用者輸入的資料符合驗證條件; 透過 sanitizers 功能,得已調整使用者輸入的資料以符合理想格式
藉由以上兩點,我們能更安心的使用這些來自使用者的資料了!
最後,由於 express-validator 是依賴 validator.js,因此有許多驗證和 Sanitizer 的功能可以依照自己的驗證需求來使用,詳細的操作說明和內容可以參考文中附上的相對應連結或文後的參考連結;當然,如果沒有找到相對應的方法,也可以客製化 validator 和 sanitizer 來做使用。