[Node.js] Express

什麼是 express

觀看 Node.js 上一文章 server.js 的 code,在那樣的情況下編寫不算太困難,但如果開始添加更複雜的路徑或是處理表單… 等,要添加更多的伺服器邏輯,那麼它將會變得相當混亂,難以管理。

而 Express 是一個能幫助我們更輕鬆管理該情況的框架,它能使我們的 code 更容易閱讀、更新和擴增。

首先透過 npmjs.com 搜尋 express,看到安裝的部分


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


接著輸入安裝 express 的指令 npm install express


透過 package.json 的 dependencies 確認已安裝 express


建立 express app

可以看 express 官方文件學習如何使用 express,以下我們直接示範,並與之前的 server.js 做比較

在專案建立一個 app.js 檔


在 app.js 輸入以下 code

1
2
const express = require('express');
const app = express();

以上表示 express 模組回傳一個函數,而我們將該函數存儲在 express 這個常數,接著調用該函數回傳一個物件並存儲在 app。
之後的操作都要藉由 app 物件來完成,就像之前的 server 物件。

在 server.js 監聽伺服器使用 listen() 方法,express 也有,表示監聽端口號 3000 發出的請求

1
app.listen(3000); // listen for requests

現在如果想要監聽 get 方法的請求,使用 app.get() 參數有兩個,第一個為想監聽的 URL,就像是之前 switch 中的各個 case,第二個參數為 callback function,包含了 req、 res 兩個物件參數,讓我們可以像之前一樣處理請求以及響應的物件

1
2
3
app.get('/', (req, res) => {

});

接著使用 res 物件處理響應的部分,可以與 server.js 一樣使用 res.write()res.end(),但這裡我們使用 express 的方式 res.send(),可以直接在其中輸入要發送的內容

1
2
3
app.get('/', (req, res) => {
res.send('<p>Home Page</p>');
});

使用 res.send() 的好處是它會先推斷出我們打算響應給瀏覽器的內容類型(Content-Type),它會自動的為我們設置內容類型標頭,也就是我們不需要手動設置

1
res.setHeader('Content-Type','text/html');

另一個好處是它會為我們自動判斷狀態代碼(status code),像這裡我們執行將發送 HTML 給瀏覽器,狀態會是 200。

實際執行
全部的 code

1
2
3
4
5
6
7
8
const express = require('express');
const app = express();

app.listen(3000); // listen for requests

app.get('/', (req, res) => {
res.send('<p>Home Page</p>');
});

這裡使用 nodemon 執行

一樣連上 localhost:3000


顯示畫面


打開開發人員工具檢查狀態為 200


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


Routing & HTML

現在試著連結 localhost:3000/about

結果顯示如下,是因為我們沒有針對 /about 做響應

只要像前面做的一樣,使用 app.get() 對 /about 做處理即可

1
2
3
4
5
6
7
8
9
10
11
12
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>');
});

執行,再次連上 localhost:3000/about

但我們不可能用這樣的方式傳一個 HTML 頁面,而是建立一個單獨的 HTML 文件,server.js 使用 fs 檔案系統模組來發送這樣的一個 HTML 文檔,這裡則不需要 fs 模組也能做到。

首先將之前專案的 views 複製一份到該專案,裡面包含 index.html、about.html、404.html


接著使用 res.sendFile(),第一個參數為檔案路徑,但由於使用相對路徑,所以必須加上第二個參數告訴 express 根目錄,如此才能知道檔案路徑是相對於何者,這裡第二個參數將根目錄設為 app.js 的目錄路徑也就是 'D:/Desktop/NODE-TEST',使用 __dirname 表示

1
2
3
4
app.get('/', (req, res) => {
// res.send('<p>Home Page</p>');
res.sendFile('./views/index.html', {root: __dirname});
});

但也可以將第一個參數使用絕對路徑便不需設置 root,但筆者不推薦,因為這僅限於在自己的電腦上運行,若是將 code 託管到別的主機便無法正常運作

1
2
3
4
app.get('/', (req, res) => {
// res.send('<p>Home Page</p>');
res.sendFile('D:/Desktop/NODE-TEST/views/index.html');
});

將 /about 也使用同樣方式設置 about.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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});
});

執行,連接 localhost:3000,顯示 index.html


連接 localhost:3000/about,顯示 about.html


接著我們可以在 index.html 及 about.html 都添加導覽列

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!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>

這樣就不用在網址輸入切換,直接點擊連結就好


Redirects & 404 pages

前面說明了怎麼監聽請求、響應 HTML 頁面,這些都在 server.js 做過,接著再來說說 express 在重新定向及 404 頁面的部分。

這裡我們將設置 /about-us 重新定向到 /about,首先監聽 '/about-us',重新定向的部分使用 res.redirect(),參數為要導向的 URL

1
2
3
app.get('/about-us', (req, res) => {
res.redirect('/about'); // redirects
});

全部的 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
});

執行,在網址欄輸入 localhost:3000/about-us
結果自然導向 /about 頁面,狀態為 301

使用 res.redirect() 比起在 server.js 的方式要簡單的多,且會自動判斷狀態代碼。

最後將找不到的頁面導向 404.html 的部分使用 app.use() 稱為中介軟體,使用起來像前面的 app.get(),但可以不添加路徑(第一個參數),結果會每當收到請求時,就會執行此函數,也就是不限於特定的 URL 都會執行

1
2
3
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 放在最後的部分,當前面的路徑都不匹配最後自然就會執行最後的這一段,與 switchdefault 有異曲同工之妙。

不過要注意使用這種方式要手動設置 status code,否則會顯示 200,這裡可以使用鏈式寫法的方式撰寫,因為 res.status() 會回傳物件本身,當然要分開寫也是 ok 的。

1
2
3
app.use((req, res) => {
res.status(404).sendFile('./views/404.html', {root: __dirname});
});

全部的 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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


且狀態為 404


這裡我們再試著將 app.use() 往上提,放在 '/' 後面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
});

執行,在網址輸入 localhost:3000,正常顯示 index.html 頁面


但輸入 localhost:3000/aboutlocalhost:3000/about-us 都會顯示 404 頁面,狀態也是 404

就如前面所說。

不過 app.use() 也可以添加裝載路徑的第一個參數,就像 app.get() 一樣,但不同的是會對該路徑上任何類型的 HTTP 要求執行此函數,前面出現的 get 就是 HTTP 要求的一種類型,其它還有 put、post … 等等類型,這些可以參考 express 的官方文件有更多的說明示範。

學到這裡可以與之前的 server.js 比較,發現用到的 code 更簡短、可讀性較高、更容易維護,所以之後就不用 server.js 的方式在後續的學習上了。


參考資料
The Net Ninja | Node.js Crash Course Tutorial #6 - Express Apps