認識 Express 中的 Middleware

Express 的本體。

什麼是 Middleware

先來看一張圖:

miidleware

簡單來說,Middleware 就是收到 request 後要做什麼處理的 function。

所以你仔細看就會發現 Express 就是各種 Middleware 來組成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 
app.use 代表一開始就會先執行的全域 Middleware:
bodyParser: 解析 request body
session: 建立 session 機制
connect-flash: 一閃而過的訊息
*/
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
}))
app.use(flash());

// 自訂的 Middleware,用來儲存給 view 存取的變數
app.use((req, res, next) => {
res.locals.username = req.session.username;
res.locals.errorMessage = req.flash('errorMessage');
next();
})
// 自訂的 Middleware,用來回到上一頁
const toPreviousPage = (req, res) => {
return res.redirect('back');
}

// 根據路由收到的 request,依序丟給 middleware1, middleware2... 來處理,以此類推
app.get('/login', usersController.login);
app.post('/login', usersController.handleLogin, toPreviousPage);
app.get('/logout', usersController.logout);
app.get('/register', usersController.register);
app.post('/register', usersController.handleRegister, toPreviousPage);

只要是 Middleware 都會有三個參數:reqresnext

1
2
3
4
5
function middleware (req, res, next) {
// 在這裡面就可以用 req, res 做任何事
// 接著再呼叫 next(),把控制權交給下個 middleware
next()
}

實際範例

先來介紹 app.use,可以想成是「全域的 Middleware」,它會影響到所有路由:

1
2
3
4
5
6
app.use((req, res, next) => {
console.log('now: ', new Date()); // 在 console 顯示目前時間
next(); // 交給下個 middleware,這邊的下一個是 todoController
});
app.get('/todos', todoController.getAll)
app.get('/todos/:id', todoController.get)

所以只要不管我到 /todos/todos/:id,console 裡都會顯示目前時間,因為它的順序是這樣的:

顯示時間的 Middleware > 顯示 todo 的 Middleware(todoContoller)

但有些時候我們可能只想把 Middleware 綁在特定的路由上,這時候就可以這樣寫:

1
2
3
4
5
6
7
8
function localMiddleware (req, res, next) {
console.log('now: ', new Date());
next();
}
// 只綁在這裡,當 localMiddleware 執行 next 後才會丟給 todoController
app.get('/todos', localMiddleware, todoController.getAll)
// 這裡不會套用
app.get('/todos/:id', todoController.get)

為什麼需要 Middleware

前面介紹了這麼多,還沒講到一個重點,為什麼需要 Middleware?

來舉個例子,如果我想要取得 request body 的內容的話怎麼辦?你可能很直覺的這樣寫:

(假設是一個新增 todo 的 request)

1
2
3
app.post('/todos', (req, res) => {
console.log(req.body);
})

這樣的想法是正確的。但很抱歉,Express 預設的 Middleware 是沒有提供這個功能的,你必須透過另外一個 Middleware,讓它先處理好 request body 的部分後,才傳給你現在的 Middleware,大概會長這樣:

1
2
3
4
5
6
// 引入別人寫好的 middleware
const bodyParser = require('body-parser');
// 解析 Form 表單(application/x-www-form-urlencoded)
app.use(bodyParser.urlencoded({ extended: true }));
// 解析 JSON
app.use(bodyParser.json());

剛有說過 app.use 會是被視為全域的 Middleware,所以接下來在其他的 Middleware 裡面都可以直接用 req.body.key 來拿內容。

剛剛是騙你的,其實在 4.0 以後 Express 有提供內建的 Middleware,所以不需要在下載 body-parser:

1
2
3
// 改成 express.urlencoded 和 express.json
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

順道一提,Express 內建的 middleware 有提供取得 query string 的功能:

1
2
3
app.get('/'. (req, res, next) => {
console.log(req.query); // {key: value}
})

其他注意事項

  1. res.end 後不可以再呼叫 next(),因為 response 已經送出去了。
  1. INSERT 指令會拿到的 result:
1
2
3
4
5
6
7
8
9
10
result: OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 4,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}

資訊蠻多的,但最常用的應該會是 affectedRowsinsertId

  1. ejs 有分 =- 的差別

= 會自動做文字跳脫,- 不會。

1
2
<%= '<h1>safe</h1>' %>
<%- '<h1>unsafe</h1>' %>
  1. 關於 flash 和 session

請務必記得這個順序:

1
2
3
4
5
6
7
8
9
10
const flash = require('connect-flash');
const session = require('express-session');
// session 先
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
}))
// 接著才可以用 flash
app.use(flash());
使用 Express 時的注意事項,還有一些地雷 MVC 架構與基本範例
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×