Middleware 中間件
- 以 Express 框架來說,從發出 request 到回傳 response 的這串過程中,會經過一系列的 middleware。
- request -> middleware -> middleware -> .... -> response
- 用途:
- Express 內建不會去解析 request body 裡面的內容(Post、delete)、session 管理機制,所以需要靠 middleware 幫它完成
- 用
app.get
可以用req.query
去抓到網址上的 query string,但用 Post 的話就沒辦法抓,這時 middleware 就會派上用場,要用 body-parser 才拿得到 request body 裡面的資料
範例ㄧ
index.js
新增以下程式碼:
app.use((req, res) => {
console.log('time:', new Date())
res.end()
})
瀏覽器跑不出內容,因為沒有在 cb 裡面加上 next,next 的功用是把控制權發到下一個 middleware 去
index.js
修正程式碼為,把控制權交給下一個 middleware:
app.use((req, res, next) => {
console.log('time:', new Date())
next()
})
範例二:實作權限管理機制
只有在網址上有 admin = true 的時候才有權限看到 todo 的東西
index.js
- 範例ㄧ
app.use((req, res, next) => {
if(req.query.admin === '1') {
next() // 如果有輸入就會跳到下一個 middleware
} else {
res.end('Error') // 如果 URL 沒有輸入 adming = 1 的話就會顯示 Error
}
})
- 範例二,把權限管理用函式包起來,並當作
app.get()
的參式傳入,可以決定哪一個路由要作權限管理
function checkPermission(req, res, next) {
if(req.query.admin === '1') {
next()
} else {
res.end('Error') // 如果 URL 沒有輸入 adming = 1 的話就會顯示 Error
}
}
app.get('/todos', checkPermission, todoController.getAll) // 只有這個路由會被影響到
介紹各個 middleware
用 body-parser 來實作新增留言機制
用 POST 的話,要使用 body-parser
在沒辦法從網址列拿到 request body 時,可以使用 body-parser
安裝:npm install body-parser
index.js
const express = require('express')
const bodyParser = require('body-parser')
const db = require('./db')
const app = express() // express 引入進來的東西是 function,去執行它就可以建立一個 app
const port = 5001
const todoController = require('./controllers/todo'
)
app.set('view engine', 'ejs')
// 一般在瀏覽器 Post 的資料就是 urlencoded 這個 content-type
app.use(bodyParser.urlencoded({ extended: false }))
// 用 ajax 的時候可能是這個 content-type
app.use(bodyParser.json())
app.post('/todos', todoController.newTodo)
app.get('/todos', todoController.getAll)
app.get('/todos/:id', todoController.get)
app.get('/', todoController.addTodo)
app.listen(port, () => {
db.connect()
console.log(`hello world, listening on port ${port}`)
})
controller
- todo.js
const todoModel = require('../models/todo')
const todoController = {
getAll: (req, res) => {
todoModel.getAll((error, results) => {
if (error) return console.log(error)
res.render('todos', { // 交給 view engine 去作
todos: results
})
})
},
get: (req, res) => {
const id = req.params.id
todoModel.get(id, (error, results) => {
if (error) return console.log(error)
res.render('todo', {
todo: results[0] // 因為即使只有一筆資料,也會是 array
})
})
},
addTodo: (req, res) => {
res.render('addTodo') // 負責 render 頁面,不是真的處理新增 todo 這個動作
},
newTodo: (req, res) => {
const content = req.body.content
todoModel.add(content, (error) => {
if (error) return console.log(error)
res.redirect('/todos') // 有錯誤的話就重新導回 /todos 這個頁面
})
/* 這邊的 content 對應到在 addTodos.ejs 裡面填的 name"content",
如果沒有 body-parser(去解析 reqest 的 body),
req.body 就會是 undefined
res.end(content) // 到瀏覽器輸入 123 會得到 123
*/
}
}
module.exports = todoController
model
- todo.js
const db = require('../db')
const todoModel = {
getAll: (cb) => { //用 callback function 拿資料
db.query(
'SELECT * FROM demo_todo', (error, results) => {
if (error) return cb(error);
cb(null, results)
});
},
get: (id, cb) => {
db.query(
'SELECT * FROM demo_todo WHERE id = ?', [id], // 這邊用類似 prepare statement 的做法(可以搜尋 Escaping query values)
(error, results) => {
if (error) return cb(error);
cb(null, results)
});
},
add: (content, cb) => {
db.query(
'INSERT INTO demo_todo (content) VALUES (?)', [content], // 這邊用類似 prepare statement 的做法(可以搜尋 Escaping query values)
(error, results) => {
if (error) return cb(error);
cb(null)
});
}
}
module.exports = todoModel
view
- addTodo.ejs
<h1>Add Todo</h1>
<form method="POST" action="/todos">
Content: <input type="text" name="content" />
<input type="submit" />
</form>
- todos.ejs
<h1>Todos</h1>
<a href="/">addTodo</a>
<ul>
<% for(let i = 0; i < todos.length; i++) { %>
<li><%= todos[i].id %>: <%= todos[i].content %></li> <!--前面加等號,代表後面的東西要輸出-->
<% } %>
</ul>
用 Session middleware
前置作業
安裝 express-session:npm install express-session
index.js
- 安裝好 express-session 後,用 require 將其引入
const express = require('express')
const bodyParser = require('body-parser')
const session = require('express-session')
const db = require('./db')
const app = express() // express 引入進來的東西是 function,去執行它就可以建立一個 app
const port = 5001
const todoController = require('./controllers/todo'
)
app.set('view engine', 'ejs')
// 一般在瀏覽器 Post 的資料就是 urlencoded 這個 content-type
app.use(bodyParser.urlencoded({ extended: false }))
// 用 ajax 的時候可能是這個 content-type
app.use(bodyParser.json())
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
}))
app.post('/todos', todoController.newTodo)
app.get('/todos', todoController.getAll)
app.get('/todos/:id', todoController.get)
app.get('/', todoController.addTodo)
app.get('/login', (req, res) => {
res.render('login') // login 的畫面,也就是 login.ejs 這個檔案
} )
app.post('/login', (req, res) => {
if (req.body.password === "abc") {
req.session.isLogin = true // req.session.<key> 類似 PHP 的 $_SESSION['<key>']
res.redirect('/') // 導回主頁面,所以要去主畫面作權限管理機制
} else {
res.redirect('/login')
}
})
app.get('/logout', (req, res) => {
req.session.isLogin = false
res.redirect('/')
})
app.listen(port, () => {
db.connect()
console.log(`hello world, listening on port ${port}`)
})
controllers
- todo.js
const todoModel = require('../models/todo')
const todoController = {
getAll: (req, res) => {
todoModel.getAll((error, results) => {
if (error) return console.log(error)
res.render('todos', { // 交給 view engine 去作
todos: results
})
})
},
get: (req, res) => {
const id = req.params.id
todoModel.get(id, (error, results) => {
if (error) return console.log(error)
res.render('todo', {
todo: results[0] // 因為即使只有一筆資料,也會是 array
})
})
},
addTodo: (req, res) => {
res.render('addTodo', {
isLogin: req.session.isLogin
}) // 負責 render 頁面,不是真的處理新增 todo 這個動作
},
newTodo: (req, res) => {
const content = req.body.content
todoModel.add(content, (error) => {
if (error) return console.log(error)
res.redirect('/todos') // 有錯誤的話就重新導回 /todos 這個頁面
})
/* 這邊的 content 對應到在 addTodos.ejs 裡面填的 name"content",
如果沒有 body-parser(去解析 reqest 的 body),
req.body 就會是 undefined
res.end(content) // 到瀏覽器輸入 123 會得到 123
*/
}
}
module.exports = todoController
View
- addTodo.ejs
<h1>Add Todo</h1>
<% if (isLogin) { %>
你已經登入 <a href="/logout">logout</a>
<% } else { %>
你還沒登入
<% } %>
<form method="POST" action="/todos">
Content: <input type="text" name="content" />
<input type="submit" />
</form>
- login.ejs
<h1>Login</h1>
<form method="POST" action="/login">
Password: <input type="password" name="password" />
<input type="submit" />
</form>
connect-flash
之前在寫 PHP 時,會將登入失敗⋯⋯等分別用不同的錯誤訊息顯示,而現在我們要用 connect-flash 來取代此功能——將訊息寫到 flash 裡面,並且在 display 後,將其消除。
前置作業
安裝:npm install connect-flash
在 index.js
引入 const flash = require('connect-flash')
及 app.use(flash())
index.js
- 須注意程式碼的先後順序
const express = require('express')
const bodyParser = require('body-parser')
const session = require('express-session')
const flash = require('connect-flash')
const db = require('./db')
const app = express()
const port = 5001
const todoController = require('./controllers/todo'
)
app.set('view engine', 'ejs')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(flash())
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
}))
app.use((req, res, next) => { // 在 express 上自己加捷徑(middleware)
res.locals.isLogin = req.session.isLogin || false // locals 裡面的訊息,可以直接在 view 裡面拿到
res.locals.errorMessage = req.flash('errorMessage')
next() // 要記得用 next() 不然會卡住
})
app.post('/todos', todoController.newTodo)
app.get('/todos', todoController.getAll)
app.get('/todos/:id', todoController.get)
app.get('/', todoController.addTodo)
app.get('/login', (req, res) => {
res.render('login' /* ,{
errorMessage: req.flash('errorMessage')
// 用對應的 key 拿出存在 flash 裡面的 value,因為有用捷徑,所以這行可以省略
}*/)
} )
app.post('/login', (req, res) => {
if (req.body.password === "abc") {
req.session.isLogin = true
res.redirect('/')
} else {
// 寫入的時候用兩個參數(key, value),用的時候用一個參數讀取
req.flash('errorMessage', 'Please input the correct password')
res.redirect('/login')
}
})
app.get('/logout', (req, res) => {
req.session.isLogin = false
res.redirect('/')
})
app.listen(port, () => {
db.connect()
console.log(`hello world, listening on port ${port}`)
})
controlllers
- todo.js 裡面的 addTodo 將參數拿掉,因為
res.locals.isLogin
已經直接將參數送到 views 那邊了
addTodo: (req, res) => {
res.render('addTodo'/* , {
因為用了 locals 所以這行不用
isLogin: req.session.isLogin
}
*/
)
},
view
- login.ejs 新增錯誤訊息
<h1>Login</h1>
<h2><%= errorMessage %></h2>
<form method="POST" action="/login">
Password: <input type="password" name="password" />
<input type="submit" />
</form>