在 全端開發者必懂的「產品搭建程序」中提過:產品依序由地基、結構、建設逐步打造。當伺服器架設完畢後,下一個關鍵階段就是路由設計,而路由設計的第一步就是取得請求(resquest)。本筆記將彙整如何取得請求的資訊及怎麼應用。
本篇筆記將解決以下問題:
誰適合閱讀:
電影清單動態路由示意圖 from Alpha Camp's material
很多網站內都含有許多類似資訊,例如電商網站的商品、電影/餐廳/書籍清單的項目等。若這麼大量的資訊,每一項都需要對應一條路由和一個局部樣版頁面,不只耗費資源、程式碼冗長並難以管理。身為動不動就想自動化的開發者,一定有更快的方式來解決,那就是——「透過 URL 路徑設計來打造動態路由」。
:params
?有別於寫死的靜態路由(例如:清單頁就使用 /list/
、編輯的頁面使用 /edit
等),動態路由能在路徑內容為合法的字串時,導引到指定的頁面。
例如製作電影清單時,使用 /movies/1
、/movies/2
、/movies/3
等各自對應特定電影時,我們無需一一建立路由,僅需以一個動態路由 /movies/:id
就能搞定!
使用方式也很直觀,以電影清單為例分為以下四個步驟:
在 用 Handlebars 做出保留原始資料的好用表單 中,稍微提到過 haldlebars 提供的一個好用方法: {{#each}} {{/each}}
。透過這了方式,利用大量格式相似的資料,產生版型一致的網頁元件,並且在按鈕或連結中鑲嵌特定的路徑,使其帶有該資料的 id 或編碼。
<!-- movie list -->
<div class="row" id="data-panel">
{{#each movies}}
<a href="/movies/{{this.id}}">
{{this.title}}
</a>
{{/each}}
</div>
只要在路徑設定中加入 :pararms
即可,其中 pararms 可任意命名,例如 id、name、title 等。
app.get('/movies/:id', (req, res) => {
// ...
})
params in console from Alpha Camp's material
既然資訊是從 req
送來的,就可以用 console.log(req)
嘗試把請求資訊印出來看看。在當中就能找到 req.params
的資訊。
app.get('/movies/:id', (req, res) => {
const id = req.params.id
// ...
})
取出後與資料庫或資料表中的資訊比對後,即可用來渲染前端頁面。
app.get('/movies/:id', (req, res) => {
const id = req.params.id
const movie = movieList.results.find(movie => movie.id.toString() === id)
res.render('show', { movie: movie })
})
電影清單搜尋功能示意圖 from Alpha Camp's material
當網頁上的資料量開始變多,我們會想使用搜尋來找到特定資訊。如果觀察任意網站上的站內搜尋,可能會在送出搜尋後看到網址產生如下的變化:
https://example.com/search?keyword=macbook
query
?網址最後的參數 ?keyword=macbook
就是所謂的 query string,這同時方便使用者看見自己搜尋的內容,並且讓我們可以截取 URL 上的搜尋字串。
使用方法可以分為以下四個步驟:
query
通常我們使用 HTML 的 <form>
表單搭配 text <input>
及 submit <button>
來設計搜尋框。
<!-- index.handlebars -->
<form action="/search">
<div class="input-group mb-3">
<input type="text" name="keyword" class="form-control" placeholder="Enter movie name to search..." aria-label="Movie Name..." aria-describedby="search-button">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit" id="search-button">Search</button>
</div>
</div>
</form>
<form action="/search">
可一目瞭然路徑用於搜尋<input name="keyword">
協助我們知道這個參數是搜尋的關鍵字query
query in console from Alpha Camp's material
試著把整個 req
印出來,可以找到 query
就藏在其中。所以可以透過 req.query
擷取 URL 上的 query
:
// app.js
app.get('/search', (req, res) => {
console.log('req.query', req.query)
// ...
})
query
並搜尋符合的資料再次以電影清單為例,來深入搜尋的處理邏輯:
// app.js
const movieList = require('./movies.json')
// ...
app.get('/search', (req, res) => {
const keyword = req.query.keyword.trim() // 取得 query 中的 keyword 字串,並去除頭尾空白
const movies = movieList.results.filter(movie => movie.title.toLowerCase().includes(keyword.toLowerCase()))
res.render('index', { movies: movies })
})
搜尋功能的關鍵邏輯是這行:
const movies = movieList.results.filter(movie => movie.title.toLowerCase().includes(keyword.toLowerCase()))
可以拆解成這樣讀:
const movies = movieList.results.filter(movie => {
movie.title.toLowerCase().includes(
keyword.toLowerCase()
)
})
Array.prototype.filter()
篩選符合回傳條件的資料String.prototype.includes()
比對字串內是否含有「傳入的片段字串」String.prototype.toLowerCase()
將兩邊的字串都變為小寫,以避免大小寫產生的搜尋誤差可以從 query
把取得的 keyword 透過 handlebars 再傳回前端介面中。
// app.js
const movieList = require('./movies.json')
// ...
app.get('/search', (req, res) => {
const keyword = req.query.keyword.trim()
// ...
res.render('index', { movies: movies, keyword: keyword }) // 把 keyword 回傳到 index.handlebars 中
})
並以 value="{{keyword}}"
來呈現。
<!-- index.handlebars -->
<form action="/search">
<div class="input-group mb-3">
<input type="text" name="keyword" class="form-control" placeholder="Enter movie name to search..." aria-label="Movie Name..."
value="{{keyword}}" aria-describedby="search-button">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit" id="search-button">Search</button>
</div>
</div>
</form>
前面我們看到透過 <form method="GET">
能把請求資訊呈現在 URL 上;但並非所有的資訊我們都想赤裸裸地放在網址上,所以我們能透過表單的另一個方法 method="POST"
來隱藏資訊。
GET
及 POST
的差異?GET
與 POST
最大的差異就是,資訊是呈現在 URL 上或放在 request body 中。可以用郵寄來理解這個概念:
GET
像寄明信片,所以資訊一覽無遺。POST
似寄平信,除了地址,其他資訊是包裹在信封內。特別注意的是,從資安角度來談,兩者的安全程度差不多低。因為不管哪個方法,資訊都可被擷取,所以通常會使用「加密」事先處理機敏資料(如:帳號、密碼等)。
但通常我們無法直接取得 POST
過來的 request body,所以需要分兩步驟,並透過額外的套件 body-parser 來協助:
$ npm install body-parser
透過 Terminal 安裝完畢後需要進行以下設定:
// app.js
// Include packages and define server related variables
const express = require('express')
const exphbs = require('express-handlebars')
const bodyParser = require('body-parser')
// ...
// setting template engine
// ...
// setting body-parser
app.use(bodyParser.urlencoded({ extended: true }))
// setting routes
require()
引入app.use()
的方式插入在其他路由之前,目的是讓進來的所有 request 優先經過 body-parser 處理。接著就能 以 POST /
的路由設定及 req.body
來取得表單資訊了!
// app.js
// ...
// setting routes
app.post('/', (req, res) => {
console.log('req.body', req.body)
res.render('index')
})
關於本系列更多內容及導讀,請閱讀作者於 Medium 個人專欄 【無限賽局玩家 Infinite Gamer | Publication – 】 上的文章 《用 JavaScript 打造全端產品的入門學習筆記》系列指南。