昨天我們完成了使用者在 LINE 上的完整下單流程,但資料僅存在伺服器記憶體,一旦重啟就會消失。今天,我們要設計後端 API,將訂單資料寫入 MongoDB,讓系統能「記住」每一筆交易。這是從 Prototype 邁向實用系統的重要一步。
分離責任:將 LINE Webhook 事件與資料存取拆開,程式結構更清晰。
擴充性:API 作為前端(LINE Bot、未來 LIFF 表單)與後端(DB)的橋樑。
可維護性:日後要接入後台或其他服務,只要呼叫相同的 API 即可。
設計一個 POST /orders
API。
請求 Payload:
lineUserId: string
(來自 LINE 的使用者 ID)
items: Array<{ productName: string; quantity: number; price: number }>
status?: 'Pending' | 'In Progress' | 'Completed'
(預設 Pending
)
資料模型對齊:完全符合 Day 8 的 OrderSchema
(userId:ObjectId
、items: orderItemSchema[]
、status
enum)。
今天完成後的效果如下~:
// routes/orders.js
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Order = require("../models/Order");
const User = require("../models/User");
/**
* Helper:以 LINE userId (string) 取得/建立 User,回傳其 ObjectId
*/
async function findOrCreateUserByLineId(lineUserId) {
let user = await User.findOne({ lineUserId });
if (!user) {
user = await User.create({ lineUserId });
}
return user._id;
}
// 建立新訂單(符合 Day 8 定義的 OrderSchema)
router.post("/", async (req, res) => {
try {
const { lineUserId, items, status } = req.body;
if (!lineUserId || !Array.isArray(items) || items.length === 0) {
return res.status(400).json({ success: false, message: "lineUserId 與 items 為必填" });
}
// 基本驗證與正規化
const normalizedItems = items
.map((it) => ({
productName: String(it.productName),
quantity: Number(it.quantity),
price: Number(it.price),
}))
.filter(
(it) =>
it.productName && Number.isFinite(it.quantity) && it.quantity > 0 &&
Number.isFinite(it.price) && it.price >= 0
);
if (normalizedItems.length !== items.length) {
return res.status(400).json({ success: false, message: "items 格式不正確" });
}
const userId = await findOrCreateUserByLineId(lineUserId);
const order = await Order.create({
userId, // ObjectId 參照 User
items: normalizedItems, // [{ productName, quantity, price }]
status: status || "Pending", // enum: Pending/In Progress/Completed
});
res.json({ success: true, orderId: order._id });
} catch (err) {
console.error(err);
res.status(500).json({ success: false, message: "訂單寫入失敗" });
}
});
module.exports = router;
// index.js
const express = require("express");
const mongoose = require("mongoose");
const app = express();
app.use(express.json());
// 引入 routes
const orderRoutes = require("./routes/orders");
app.use("/orders", orderRoutes);
// MongoDB 連線
mongoose.connect("mongodb://localhost:27017/line-order-system")
.then(() => console.log("MongoDB Connected"))
.catch(err => console.error(err));
app.listen(3000, () => console.log("Server running on port 3000"));
在 Day 10 的 handleEvent
中,使用者輸入「確認」時,除了回覆 LINE 訊息,也要呼叫 POST /orders
把資料寫進 DB(記得在檔案頂部 const axios = require("axios");
)。
if (event.message.text === "確認") {
await axios.post("http://localhost:3000/orders", {
lineUserId: event.source.userId, // 由 LINE 事件而來
items: [
{
productName: state.item,
quantity: state.quantity,
price: state.price, // ⚠️ 實務上請以後端商品表為準,不要信任前端價格
},
],
});
delete userState[event.source.userId];
return client.replyMessage(event.replyToken, {
type: "text",
text: "訂單已送出,並成功寫入資料庫!",
});
}
等正確執行完就可以去 MongoDB Compass 即時查看資料有沒有寫進去啦!
成功:回傳 { success: true, orderId }
。
失敗:回傳 { success: false, message }
。
加上 try/catch
,避免 DB 錯誤導致伺服器崩潰。
學會如何把 LINE Bot 的訂單流程與 MongoDB 串接。
API 設計是未來擴充的基礎,任何前端(LINE、LIFF、Web 後台)都能共用同一套介面。
Prototype 與實務系統的差異就在於 資料必須持久化,今天完成了最關鍵的一步!
Day 12:將會告訴大家,收到訂單然後呢?該如何通知店家訂單來了!