# Node.js(六) Express
###### tags: `Node.js`
<br />
## 什麼是 express
觀看上一筆記 server.js 的 code,在那樣的情況下編寫不算太困難,但如果開始添加更複雜的路徑或是處理表單… 等,要添加更多的伺服器邏輯,那麼它將會變得相當混亂,難以管理。
而 Express 是一個能幫助我們更輕鬆管理該情況的框架,它能使我們的 code 更容易閱讀、更新和擴增。
首先透過 npmjs.com 搜尋 express,看到安裝的部分

<br />
這裡筆者開啟一個空的專案,並在終端輸入 `npm init` 指令,初始化專案並產生 package.json 檔案

<br />
接著輸入安裝 express 的指令 `npm install express`

<br />
透過 package.json 的 dependencies 確認已安裝 express

<br />
## 建立 express app
可以看 express 官方文件學習如何使用 express,以下我們直接示範,並與之前的 server.js 做比較
在專案建立一個 app.js 檔

<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 執行

一樣連上 localhost:3000

<br />
顯示畫面

<br />
打開開發人員工具檢查狀態為 200

<br />
Content-Type 也自動設置為 text/html

<br />
## Routing & HTML
現在試著連結 `localhost:3000/about`
結果顯示如下,是因為我們沒有針對 /about 做響應

只要像前面做的一樣,使用 `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`

但我們不可能用這樣的方式傳一個 HTML 頁面,而是建立一個單獨的 HTML 文件,server.js 使用 fs 檔案系統模組來發送這樣的一個 HTML 文檔,這裡則不需要 fs 模組也能做到。
首先將之前專案的 views 複製一份到該專案,裡面包含 index.html、about.html、404.html

<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

<br />
連接 `localhost:3000/about`,顯示 about.html

<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 />
這樣就不用在網址輸入切換,直接點擊連結就好

<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

使用 `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

<br />
且狀態為 404

<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 頁面

<br />
但輸入 `localhost:3000/about` 或 `localhost:3000/about-us` 都會顯示 404 頁面,狀態也是 404

就如前面所說。
不過 `app.use()` 也可以添加裝載路徑的第一個參數,就像 `app.get()` 一樣,但不同的是會對該路徑上任何類型的 HTTP 要求執行此函數,前面出現的 get 就是 HTTP 要求的一種類型,其它還有 put、post ... 等等類型,這些可以參考 express 的官方文件有更多的說明示範。
<br />
學到這裡可以與之前的 server.js 比較,發現用到的 code 更簡短、可讀性較高、更容易維護,所以之後就不用 server.js 的方式在後續的學習上了。