# Node.js(六) Express ###### tags: `Node.js` <br /> ## 什麼是 express 觀看上一筆記 server.js 的 code,在那樣的情況下編寫不算太困難,但如果開始添加更複雜的路徑或是處理表單… 等,要添加更多的伺服器邏輯,那麼它將會變得相當混亂,難以管理。 而 Express 是一個能幫助我們更輕鬆管理該情況的框架,它能使我們的 code 更容易閱讀、更新和擴增。 首先透過 npmjs.com 搜尋 express,看到安裝的部分 ![](https://i.imgur.com/dHYWzmT.png) <br /> 這裡筆者開啟一個空的專案,並在終端輸入 `npm init` 指令,初始化專案並產生 package.json 檔案 ![](https://i.imgur.com/2JAqv9V.png) <br /> 接著輸入安裝 express 的指令 `npm install express` ![](https://i.imgur.com/WHzO2Xu.png) <br /> 透過 package.json 的 dependencies 確認已安裝 express ![](https://i.imgur.com/2Z0KNsa.png) <br /> ## 建立 express app 可以看 express 官方文件學習如何使用 express,以下我們直接示範,並與之前的 server.js 做比較 在專案建立一個 app.js 檔 ![](https://i.imgur.com/7Wqb5RH.png) <br /> 在 app.js 輸入以下 code ```javascript= const express = require('express'); const app = express(); ``` 以上表示 express 模組回傳一個函數,而我們將該函數存儲在 express 這個常數,接著調用該函數回傳一個物件並存儲在 app。 之後的操作都要藉由 app 物件來完成,就像之前的 server 物件。 <br /> 在 server.js 監聽伺服器使用 listen() 方法,express 也有,表示監聽端口號 3000 發出的請求 ```javascript= app.listen(3000); // listen for requests ``` <br /> 現在如果想要監聽 get 方法的請求,使用 `app.get()` 參數有兩個,第一個為想監聽的 URL,就像是之前 switch 中的各個 case,第二個參數為 callback function,包含了 req、 res 兩個物件參數,讓我們可以像之前一樣處理請求以及響應的物件 ```javascript= app.get('/', (req, res) => { }); ``` <br /> 接著使用 res 物件處理響應的部分,可以與 server.js 一樣使用 `res.write()` 及 `res.end()`,但這裡我們使用 express 的方式 `res.send()`,可以直接在其中輸入要發送的內容 ```javascript= app.get('/', (req, res) => { res.send('<p>Home Page</p>'); }); ``` 使用 `res.send()` 的好處是它會先推斷出我們打算響應給瀏覽器的內容類型(Content-Type),它會自動的為我們設置內容類型標頭,也就是我們不需要手動設置 ```javascript res.setHeader('Content-Type','text/html'); ``` 另一個好處是它會為我們自動判斷狀態代碼(status code),像這裡我們執行將發送 HTML 給瀏覽器,狀態會是 200。 <br /> **實際執行** 全部的 code ```javascript= const express = require('express'); const app = express(); app.listen(3000); // listen for requests app.get('/', (req, res) => { res.send('<p>Home Page</p>'); }); ``` <br /> 這裡使用 nodemon 執行 ![](https://i.imgur.com/0eMHWiV.png) 一樣連上 localhost:3000 ![](https://i.imgur.com/4IhulBy.png) <br /> 顯示畫面 ![](https://i.imgur.com/Cdk8qxK.png) <br /> 打開開發人員工具檢查狀態為 200 ![](https://i.imgur.com/3vzlDjr.png) <br /> Content-Type 也自動設置為 text/html ![](https://i.imgur.com/gqXdLo3.png) <br /> ## Routing & HTML 現在試著連結 `localhost:3000/about` 結果顯示如下,是因為我們沒有針對 /about 做響應 ![](https://i.imgur.com/YNHZjZh.png) 只要像前面做的一樣,使用 `app.get()` 對 /about 做處理即可 ```javascript= const express = require('express'); const app = express(); app.listen(3000); // listen for requests app.get('/', (req, res) => { res.send('<p>Home Page</p>'); }); app.get('/about', (req, res) => { res.send('<p>About Page</p>'); }); ``` <br /> 執行,再次連上 `localhost:3000/about` ![](https://i.imgur.com/VbX2E0i.png) 但我們不可能用這樣的方式傳一個 HTML 頁面,而是建立一個單獨的 HTML 文件,server.js 使用 fs 檔案系統模組來發送這樣的一個 HTML 文檔,這裡則不需要 fs 模組也能做到。 首先將之前專案的 views 複製一份到該專案,裡面包含 index.html、about.html、404.html ![](https://i.imgur.com/FYKuocr.png) <br /> 接著使用 `res.sendFile()`,第一個參數為檔案路徑,但由於使用相對路徑,所以必須加上第二個參數告訴 express 根目錄,如此才能知道檔案路徑是相對於何者,這裡第二個參數將根目錄設為 app.js 的目錄路徑也就是 `'D:/Desktop/NODE-TEST'`,使用 `__dirname` 表示 ```javascript= app.get('/', (req, res) => { // res.send('<p>Home Page</p>'); res.sendFile('./views/index.html', {root: __dirname}); }); ``` <br /> 但也可以將第一個參數使用絕對路徑便不需設置 root,但筆者不推薦,因為這僅限於在自己的電腦上運行,若是將 code 託管到別的主機便無法正常運作 ```javascript= app.get('/', (req, res) => { // res.send('<p>Home Page</p>'); res.sendFile('D:/Desktop/NODE-TEST/views/index.html'); }); ``` <br /> 將 /about 也使用同樣方式設置 about.html ```javascript= const express = require('express'); const app = express(); app.listen(3000); // listen for requests app.get('/', (req, res) => { // res.send('<p>Home Page</p>'); res.sendFile('./views/index.html', {root: __dirname}); }); app.get('/about', (req, res) => { // res.send('<p>About Page</p>'); res.sendFile('./views/about.html', {root: __dirname}); }); ``` <br /> 執行,連接 `localhost:3000`,顯示 index.html ![](https://i.imgur.com/OgoU4qR.png) <br /> 連接 `localhost:3000/about`,顯示 about.html ![](https://i.imgur.com/Z8trV77.png) <br /> 接著我們可以在 index.html 及 about.html 都添加導覽列 **index.html** ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Node.js Test</title> </head> <body> <h1>Home</h1> <p>This is a test!</p> <!-- 添加連結 --> <nav> <a href="/">Home Page</a> <a href="/about">About Page</a> </nav> </body> </html> ``` **about.html** ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Node.js Test</title> </head> <body> <h1>About</h1> <p>This is an about page.</p> <!-- 添加連結 --> <nav> <a href="/">Home Page</a> <a href="/about">About Page</a> </nav> </body> </html> ``` <br /> 這樣就不用在網址輸入切換,直接點擊連結就好 ![](https://i.imgur.com/K3FMlYr.png) <br /> ## Redirects & 404 pages 前面說明了怎麼監聽請求、響應 HTML 頁面,這些都在 server.js 做過,接著再來說說 express 在重新定向及 404 頁面的部分。 這裡我們將設置 `/about-us` 重新定向到 `/about`,首先監聽 `'/about-us'`,重新定向的部分使用 `res.redirect()`,參數為要導向的 URL ```javascript= app.get('/about-us', (req, res) => { res.redirect('/about'); // redirects }); ``` <br /> 全部的 code ```javascript= const express = require('express'); const app = express(); app.listen(3000); // listen for requests app.get('/', (req, res) => { // res.send('<p>Home Page</p>'); res.sendFile('./views/index.html', {root: __dirname}); }); app.get('/about', (req, res) => { // res.send('<p>About Page</p>'); res.sendFile('./views/about.html', {root: __dirname}); }); app.get('/about-us', (req, res) => { res.redirect('/about'); // redirects }); ``` <br /> 執行,在網址欄輸入 `localhost:3000/about-us` 結果自然導向 `/about` 頁面,狀態為 301 ![](https://i.imgur.com/Cp8JgvV.png) 使用 `res.redirect()` 比起在 server.js 的方式要簡單的多,且會自動判斷狀態代碼。 <br /> 最後將找不到的頁面導向 404.html 的部分使用 `app.use()` 稱為中介軟體,使用起來像前面的 `app.get()`,但可以不添加路徑(第一個參數),結果會每當收到請求時,就會執行此函數,也就是不限於特定的 URL 都會執行 ```javascript= app.use((req, res) => { res.sendFile('./views/404.html', {root: __dirname}); }); ``` 這裡你可能會問每次收到請求都會執行,那麼不論如何瀏覽器不都會呈現 404.html 的頁面嗎? **說明** 以上函數會對每個傳入的請求觸發,但前提是請求必須到達 code 的這一段,就像 server.js 中的 `switch` 一樣,當瀏覽器發出請求時,我們會先透過 `switch` 判斷 `req.url` 為何,自上而下的比對 case 選項,一但遇到符合的便會執行然後 break 跳出,而 express 也一樣遇到匹配的路徑就不再執行剩餘的 code,其它功能也就不會觸發。 所以簡單來說將以上的 code 放在最後的部分,當前面的路徑都不匹配最後自然就會執行最後的這一段,與 `switch` 的 `default` 有異曲同工之妙。 <br /> 不過要注意使用這種方式要手動設置 status code,否則會顯示 200,這裡可以使用鏈式寫法的方式撰寫,因為 `res.status()` 會回傳物件本身,當然要分開寫也是 ok 的。 ```javascript= app.use((req, res) => { res.status(404).sendFile('./views/404.html', {root: __dirname}); }); ``` <br /> 全部的 code ```javascript= const express = require('express'); const app = express(); app.listen(3000); // listen for requests app.get('/', (req, res) => { // res.send('<p>Home Page</p>'); res.sendFile('./views/index.html', {root: __dirname}); }); app.get('/about', (req, res) => { // res.send('<p>About Page</p>'); res.sendFile('./views/about.html', {root: __dirname}); }); app.get('/about-us', (req, res) => { res.redirect('/about'); // redirects }); app.use((req, res) => { res.status(404).sendFile('./views/404.html', {root: __dirname}); }); ``` 執行,在網址輸入 `localhost:3000/123`,顯示 404.html ![](https://i.imgur.com/HFNYdjx.png) <br /> 且狀態為 404 ![](https://i.imgur.com/4YMtVJ6.png) <br /> 這裡我們再試著將 `app.use()` 往上提,放在 `'/'` 後面 ```javascript= const express = require('express'); const app = express(); app.listen(3000); // listen for requests app.get('/', (req, res) => { // res.send('<p>Home Page</p>'); res.sendFile('./views/index.html', {root: __dirname}); }); app.use((req, res) => { res.status(404).sendFile('./views/404.html', {root: __dirname}); }); app.get('/about', (req, res) => { // res.send('<p>About Page</p>'); res.sendFile('./views/about.html', {root: __dirname}); }); app.get('/about-us', (req, res) => { res.redirect('/about'); // redirects }); ``` <br /> 執行,在網址輸入 `localhost:3000`,正常顯示 index.html 頁面 ![](https://i.imgur.com/5EIihFN.png) <br /> 但輸入 `localhost:3000/about` 或 `localhost:3000/about-us` 都會顯示 404 頁面,狀態也是 404 ![](https://i.imgur.com/fTUCjR3.png) 就如前面所說。 不過 `app.use()` 也可以添加裝載路徑的第一個參數,就像 `app.get()` 一樣,但不同的是會對該路徑上任何類型的 HTTP 要求執行此函數,前面出現的 get 就是 HTTP 要求的一種類型,其它還有 put、post ... 等等類型,這些可以參考 express 的官方文件有更多的說明示範。 <br /> 學到這裡可以與之前的 server.js 比較,發現用到的 code 更簡短、可讀性較高、更容易維護,所以之後就不用 server.js 的方式在後續的學習上了。