只是個幫助忙碌的專業人士和父母找回時間、平衡生活的斜槓老爸。 我探索人生的大小賽局,分享優化人生的實用觀點(關於人類、科技和未來)。
在我的個人網站上獲取最新的觀點:https://klkuo.guru

在 全端開發者必懂的「產品搭建程序」中提過:產品依序由地基、結構、建設逐步打造。當伺服器架設完畢後,下一個關鍵階段就是路由設計,而路由設計的第一步就是取得請求(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 打造全端產品的入門學習筆記》系列指南。