0%

Basics of Express

Express

Express 是一個輕量化且具彈性的 Node.js 網站 (web applications) 開發框架,對無論是網頁或行動應用程式,其都有充足的工具可以支援。

使用 npm 安裝 Express 到專案中:

$ npm install express --save

接著,在專案中開啟一個 app.js ,並創建一個 Express 應用程式,此為使用 Express 的起手式。

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

※ 遷移到新版,Express 3 與 Express 4 之間的差異?

以下介紹 Express 常用的 API 功能,和如何使用的範例碼。

express()

  • express.json([options])
    • 為內建的中介函式,負責解析客戶傳進來 JSON 格式的資料,此功能由 body-parser 而來。經過解析後,req.body 就會被此函式解析後的結果 (key-value pairs的形式) 取代。
1
2
// 不設路徑參數,代表對全網域都作用
app.use(express.json());
  • express.Router([options])
    • 建立一個新的 router 物件。
    • 可搭配中介層和 HTTP 方法 (get, post. put, …) ,將客戶請求導向到路由中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在主要的 app.js 中,把同一父路徑下的子路徑拆分到新檔案管理 (分層)
// app.js 與 routes 在同一資料夾下
var news = require("./routes/news");
// 遇到要進 "/news" 路徑下的請求,跑伺服器上 "./routes/news.js" 的內容
app.use("/news", news);

// 以下寫在 news.js 內
// 建立環境
var express = require("express");
var router = express.Router();

// 設定各項子路徑的回應
router.get("/breaking", function(req, res){
res.send("<h2>Latest news you should know.</h2>");
});
router.get("/international", function(req, res){
res.send("<h2>What's new around the world?</h2>");
});

// 最後,匯出這兩個回應設定
module.exports = router;
1
2
3
// 圖片、給 ejs 載入的 <script scr="/js/all.js"> 等靜態檔案
// public 下可再開設 img, js 等資料夾
app.use(express.static("public"));
  • express.urlencoded([options])
    • 為內建的中介函式,負責解析客戶傳進來 urlencoded 格式的資料,此功能由 body-parser 而來。經過解析後,req.body 就會被此函式解析後的結果 (key-value pairs的形式) 取代。
    • extended 屬性:
      • 為布林值,當 false 時,值可為字串或陣列,當 true 時,值可為任何種類。
1
2
// 不設路徑參數,代表對全網域作用
app.use(express.urlencoded({extended: false}));

Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 註冊 ejs 引擎
var engine = require("ejs-locals");
app.engine("ejs", engine);
app.set("views", "./views");
app.set("view engine", "ejs");

// 以 views 下的 product.ejs 檔案回應客戶端對 domain/product 的請求
app.get("/product", function(req, res){
res.render("product")
})

// 帶入物件給 ejs 檔使用
app.get("/index", function(req, res){
res.render("index", {
name: "Tina",
age: 31,
gender: "female",
family: "<em>Chen</em>"
isQualified: true,
interest:["hands", "jogging", "shopping"]
})
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- EJS 使用 express 傳入的物件 -->
<ul>
<li><%= name %></li>
<li><%= age %></li>
<li><%- family %></li>
<li><% if (isQualified) { %>
<em>Displayed!</em>
<% } %></li>
</ul>
<ul>
<% for (const index in interests) { %>
<li><%- interests[index] %></li>
<% } %>
</ul>
  • app.get(‘path’, callback)
    • 執行 callback 函式,以應對客戶端對某個特定路徑提出的 HTTP GET 請求。
    • ‘/‘ 根路徑,通常是網域首頁,為路徑預設值。
    • callback 為中介函式。
1
2
3
app.get('/', function(req, res){
res.send('GET request to homepage success!');
})
  • app.listen(port)
    • 綁定並監聽指定連接埠的連線狀況,與 Node 的 http.server.listen() 方法類似。
    • ※ 在一些主機服務上,如:Heroku, Nodejitsu, AWS,供應商可能會另行設置其他連接埠給你的網站使用,如果寫死埠號可能會造成 500 錯誤,以下有參考的解決方法。
1
2
3
4
5
6
7
var express = require('express');
var app = express();
// app.listen(3000); 在自己可控制的主機上可以寫死

// 若無預設的 port 則使用 3000
var port = process.env.PORT || 3000;
app.listen(port);
  • app.post(‘path’, callback)
    • 執行 callback 函式,以應對客戶端對某個特定路徑提出的 HTTP POST 請求。
    • ‘/‘ 根路徑,通常是網域首頁,為路徑預設值。
    • callback 為中介函式。
1
2
3
app.post('/', function (req, res) {
res.send('POST request to homepage')
})
  • app.set(‘name’, ‘value’)
    • 將 ‘name’ 設定成 ‘value’ ,但某些 ‘name’ 可以用來控制伺服器的行為。
      • 控制用的名字,請參考:Application Settings
        • “views”:範本檔所在的目錄
        • “view engine”:設定要用的範本引擎
1
2
3
4
5
6
7
// 一般應用
app.set('title', 'My Site');
app.get('title') // 'My Site'

// 設定 ejs 模板引擎,兩個皆為 Express 保留的控制用字
app.set("views", "./views");
app.set("view engine", "ejs");
  • app.use(‘path’, callback)
    • 對特定路徑(‘path’)置放和執行指定的中介函式,類似守門員的功用。
    • ‘/‘,預設值為根路徑。
1
2
3
4
5
6
// 不設定路徑,即對每個進入該網域的客戶請求都執行下列 callback
app.use(function(req, res, next){
console.log("Time: %d", Date.now());
// 進入後續程式碼
next();
})

Request

  • req.body
    • 包含了請求所提交的 key-value 資料對,預設上是 undefined,但再使用中介層 (如:express.json(), express.urlencoded) 解析後會變成物件資料。
1
2
3
4
5
6
7
8
9
10
// 解析 application/json 資料 
app.use(express.json())
// 解析 application/x-www-form-urlencoded 資料
app.use(express.urlencoded({ extended: true }))

// 接收對 "/profile" 提出的 POST 請求,查看客戶端送出的資料,並以 JSON 格式回應
app.post("/profile", function (req, res, next) {
console.log(req.body)
res.json(req.body)
})

Response

  • res.redirect(status, ‘path’)
    • 將客戶端網址重新導向指定的路徑,可同時指定前面的參數做為 HTTP 的狀態碼。未表明時,預設為 “302 Found” (Moved temporarily,資源存在但位置被臨時移動)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 可支援轉向到其他網域
res.redirect("https://google.com")

// 支援從根路徑出發的相對路徑寫法
res.redirect("/admin") // 導向到 http://example.com/admin

// 支援從現有網址出發的相對路徑寫法
// 現在網址:http://example.com/blog/admin/ (注意此處結尾有斜線)
res.redirect("post/new") // 導向到 http://example.com/blog/admin/post/new
// 若結尾無斜線:http://example.com/blog/admin
// 則會導到 http://example.com/blog/post/new

// 回到上一層
// 現在網址:http://example.com/admin/post/new
res.redirect("..") // http://example.com/admin/post

// 回到上一頁 (back to the referer)
res.redirect('back')
  • res.render(‘view’ [, locals] [, callback])
    • 渲染 view 檔案 (如放在 views 資料夾下的 product.ejs 就是填入 “product”),並發送渲染過的 HTML 字串給客戶端。 view 參數是位於 views 資料夾下的,要被渲染檔案的路徑 (可為絕對路徑或是相對於 views 的路徑)。
    • locals 為物件,可將本地端的變數帶給 view 檔案使用。
1
2
3
4
5
6
7
// 傳送已渲染的 view 給客戶端
res.render('index')

// 傳送本地變數給 view 的檔案使用
res.render('user', { name: 'Tobi' }, function (err, html) {
// ...
})
  • res.send([body])
    • 發送 HTTP 回應,body 參數可為 buffer 物件、字串、物件、陣列。
1
2
3
4
5
6
7
8
res.send(Buffer.from('whoop'));
// 當參數為物件或是陣列時,回應會使用 JSON 呈現
res.send({ some: 'json' });
// 當參數為字串時,"Content-Type" 會被設成 "text/html"
res.send('<p>some html</p>');
// 與狀態碼進行鏈式寫法
res.status(404).send('Sorry, we cannot find that!');
res.status(500).send({ error: 'something blew up' });
  • res.status(code)
    • 設定針對 HTTP 狀態碼的回應,通常採用鏈式寫法,後面函式為針對此狀態的回應。
1
2
3
4
5
6
7
8
9
10
11
12
// 針對全網域的 404 錯誤的回應
app.use(function(req, res){
res.status(404).send("<h2>抱歉,找不到您要的頁面</h2>")
})
// 針對全網域的 500 (伺服器程式錯誤,Internal Server Error) 的回應
app.use(function(req, res){
res.status(500).send("<h2>伺服器程式出現問題,請稍後再試</h2>")
})
// 官方範例
res.status(403).end()
res.status(400).send('Bad Request')
res.status(404).sendFile('/absolute/path/to/404.png')

Middleware

  • 中介層函式是一些有權存取請求物件 (req)、回應物件 (res)、下一個中介層 (next) 的函式。中介層函式能執行以下任務:
    • 執行任何程式碼。
    • 變更請求和回應物件。
    • 結束請求 / 回應循環。
    • 呼叫堆疊中的下一個中介層。
  • 如果當前的中介層不會結束請求 / 回應循環,則必須呼叫 next() ,才能將控制權傳遞到下一個中介層,否則請求會停擺。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 宣告一個中介層函式 myLogger
// 在通過此中介層後,console 會印出 "Logged!"
var myLogger = function(req, res, next){
console.log("Logged!");
next();
};

// 在 req 物件下新增一個 reqTime 屬性,記錄 timestamp
var reqTime = function(req, res, next){
req.reqTime = Date.now();
next();
};
app.use(reqTime);
app.get('/', function(req, res){
var resText = "Hello, guest!";
resText += "Requested at: " + req.reqTime + "";
res.send(resText);
});
app.listen(3000);