[BE201] Express & Sequelize part 3


Posted by yymarlerr on 2021-08-24

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-sessionnpm 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>









Related Posts

[進階 js 11]  this

[進階 js 11] this

HTML tag

HTML tag

AJAX - HTTP狀態碼

AJAX - HTTP狀態碼


Comments