# 甚麼是 Middleware

app.get('/', (req, res) => {
  res.send("Hello world")
})
app.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

# Middleware 可以做什麼

執行任何程式碼。
對要求和回應物件進行變更。
結束要求 / 回應循環。
呼叫堆疊中的下一個中介軟體函數。

# Middleware 語法介紹

# 參數不同的 Middleware 介紹

# 兩個參數的 Middleware

當預設 / ,回傳 Hello Woeld!

var express = require('express');
var app = express();
app.get('/',(req, res) => {
  res.send('Hello World!');
});
app.listen(3000);

# 三個參數的 Middleware

不一定要指定路徑,沒有指定代表都會觸發
每個參數的位置是固定的,雖然名字可以亂取但不會因為 req 的位置改叫 res 就變 res 的功能
如果 res 沒有使用,必須使用 next() 到下一個 middleware 不然會卡在這裡

app.use('/user/:id', logRequest, validateUserId, checkPermissions);
function logRequest(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  console.log('Request Type:', req.method);
  next();
}
function validateUserId(req, res, next) {
  if (!req.params.id) {
    return res.status(400).send('Missing ID');
  }
  next();
}
function checkPermissions(req, res, next) {
  if (req.params.id !== '123') {
    return res.status(403).send('Access Denied');
  }
  console.log('User is valid');
  next();
}

# 應用程式層次的 middleware

middleware 裡面的 function 沒有限制幾個可以一直跌加,會依序執行
為什麼不要把所有邏輯寫在同一個 middleware ?
因為大量邏輯塞在同一個 function 裡不好維護

app.use('/user/:id', logRequest, validateUserId, checkPermissions);
function logRequest(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  console.log('Request Type:', req.method);
  next();
}
function validateUserId(req, res, next) {
  if (!req.params.id) {
    return res.status(400).send('Missing ID');
  }
  next();
}
function checkPermissions(req, res, next) {
  if (req.params.id !== '123') {
    return res.status(403).send('Access Denied');
  }
  console.log('User is valid');
  next();
}

# 錯誤處理 middleware

錯誤處理中介軟體一律會使用四個引數。您必須提供這四個引數,將它識別為錯誤處理中介軟體函數。即使您不需要使用 next 物件也必須指定,以維護簽章。否則,會將 next 物件解譯為一般中介軟體,而無法處理錯誤。

app.use(function(err, req, res, next) {
  console.error(err.stack);
  console.log('I need next help.')
  next(err)
});
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

# 有無 return 的差異

在一個 middleware 沒有 return 會依序發送響應
裡面的 3 個響應都會執行
有 return 的話會提前終止響應

app.use(function(req,res) {
  res.send('Hi');
  res.send('Hi Hi');
  res.send('Hi Hi Hi Hi');
});

下列的程式碼就只會有一個 Hi 被執行而已

app.use(function(req,res) {
  return res.send('Hi');
  res.send('Hi Hi');
  res.send('Hi Hi Hi Hi');
});

# 路由器層次的 middleware

# 語法

app.use('/user', userRouter);
第一個參數是網址的前綴,後面是引入的 router
userRouter 裡的 /:id 完整網址會是 /user/:id

# 程式碼範例

// userRouter.js
var express = require('express');
var userRouter = express.Router();
userRouter.get('/:id', function (req, res) {
  res.send('User ' + req.params.id);
});
module.exports = userRouter;
// productRouter.js
var express = require('express');
var productRouter = express.Router();
productRouter.get('/:id', function (req, res) {
  res.send('Product ' + req.params.id);
});
module.exports = productRouter;
// app.js
var express = require('express');
var app = express();
var userRouter = require('./userRouter');
var productRouter = require('./productRouter');
app.use('/user', userRouter); // Mount user routes
app.use('/product', productRouter); // Mount product routes
app.listen(3000);

# 為什麼要用 Router ?

因為可以把邏輯切分開,不必把所有東西都寫在同個檔案裡
增加可讀性,可維護性
router 可以嵌套使用

# 程式範例

# 範例 1 : 密碼驗證系統

const express = require('express')
const verifyPassword = (req, res, next) => {
  const { password } = req.query
  if (password === 'ccc') {
    next()
  } else {
    throw new Error("password Error!");
  }
}
app.get('/products/new', verifyPassword, (req, res) => {
  res.render('products/new')
})
app.use((err, req, res, next) => {
  console.log("***********ERROR*************")
  res.status(500).send(err);
})

如果密碼錯誤會丟出 err ,下方是密碼錯誤的報錯
會看到第一行是我們定義的錯誤訊息,後面的內容是在告訴你程式哪邊出了問題

Error: passord Error!
    at verifyPassword (D:\Coding_training\aboutWeb\mongoTest\index.js:28:11)
    at Layer.handle [as handle_request] (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\layer.js:95:5)
    at next (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\route.js:149:13)
    at Route.dispatch (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\route.js:119:3)
    at Layer.handle [as handle_request] (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\layer.js:95:5)
    at D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\index.js:284:15
    at Function.process_params (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\index.js:346:12)
    at next (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\index.js:280:10)
    at D:\Coding_training\aboutWeb\mongoTest\index.js:38:3
    at Layer.handle [as handle_request] (D:\Coding_training\aboutWeb\mongoTest\node_modules\express\lib\router\layer.js:95:5)

# 範例 2 : 異步操作的處理方式

app.use(async (req, res, next) => {
  try {
    const data = await fetchData();
    req.data = data;
    next();
  } catch (error) {
    next(error);
  }
});

# 常見的 middleware 使用

# 範例 1

# express.urlencoded

app.use(express.urlencoded({ extended: true }))

http://localhost:3000/test?key1=value1&key2[subkey]=value2

如果沒有上述的 middleware ,伺服器將無法解析後面的 key1=value1&key2[subkey]=value2
req.body 將會是 undefine

# 補充介紹: req.query , req.params , req.body 區別

1. req.query 和 req.params 是 Express 內建支持的

  • req.query 和 req.params 是 Express 根據請求的 URL 和路由規則自動解析的。
  • 它們的數據來自 URL 本身(例如查詢字符串或路由參數),Express 無需額外的中介軟體即可提取並解析。
router.get('/', (req,res) => {
    res.send(`My id is ${req.query.id}`)
})
router.get('/test/:id', (req,res) => {
    res.send(`I am testing ${req.params.id}`)
})

2. req.body 是原始數據,需要手動解析

  • HTTP 請求體中的數據在到達 Express 應用時,仍然是未經處理的原始數據流(二進制或字符串格式)。
  • 這些原始數據需要通過中介軟體(如 express.urlencoded 或 express.json )進行解析,才能轉換成可用的 JavaScript 對象並存儲到 req.body 中。
  • 如果沒有啟用中介軟體,Express 不會自動處理這些數據,因此 req.body 默認為 undefined 。

3. 程式碼範例

<form action="/products" method="post">
  <label for="name">Product name</label>
  <input type="text" name="name" id="name" placeholder="name...">
  <label for="price">Product price</label>
  <input type="number" name="price" id="price" placeholder="price...">
  <label for="category">Product category</label>
  <select name="categories" id="">
    <option value="fruit">fruit</option>
    <option value="vegetable">vegetable</option>
    <option value="dairy">dairy</option>
  </select>
  <button>Submit</button>
</form>
app.post('/products', async (req, res) => {
  console.log(req.body)
  res.send("Creat a new product")
})

範例 body 內部的資料 { name: 'testProduct', price: '888', categories: 'fruit' }
網址: name=testProduct&price=888&categories=fruit
如果無法解析: undefined

# 官方文檔

https://expressjs.com/zh-tw/guide/using-middleware.html