今天我們要在 src/routes/holdings.ts 加上一組 POST API,讓朋友可以把自己新抓到的寶可夢存進資料庫裡。在這篇實作中,我們將探討後端工程師絕對不能犯的低級錯誤:SQL Injection (資料隱碼攻擊)。
當前端把新增的資料 (例如寶可夢 ID、備註文字) 傳給後端時,我們要怎麼寫 SQL?
| 寫法 | 程式碼範例 | 危險程度與原理 |
|---|---|---|
| ❌ 錯誤寫法 (字串串接) | sql = "INSERT ... VALUES ('" + req.body.notes + "')" |
極度致命! 如果駭客在 notes 欄位輸入 '); DROP TABLE system_users; --,這段惡意字串會直接變成 SQL 指令被執行,您的帳號表會瞬間灰飛煙滅! |
| ✅ 正確寫法 (參數化查詢) | sql = "INSERT ... VALUES (?)", args: [req.body.notes] |
絕對安全。 資料庫引擎會把傳入的 ? 嚴格當作「純文字資料」看待,即便裡面包含 SQL 關鍵字,也只會變成一段名叫 "DROP TABLE..." 的搞笑備註而已,完全不會被當成指令執行。 |
我們使用了 libsql 提供的 args 陣列來實作完美的參數化查詢:
router.post('/', async (req, res) => {
const { pokemon_id, player_id, gender, is_shiny, is_lucky, notes } = req.body;
try {
// 使用 ? 作為佔位符,把變數放在 args 陣列中
// turso (libsql) 會自動幫我們做好防護
await db.execute({
sql: `
INSERT INTO user_pokemon_holdings
(pokemon_id, player_id, gender, is_shiny, is_lucky, notes)
VALUES (?, ?, ?, ?, ?, ?)
`,
args: [
pokemon_id,
player_id,
gender ?? null, // 處理 undefined 以免 libsql 報錯
is_shiny ? 1 : 0,
is_lucky ? 1 : 0,
notes ?? null
]
});
res.status(201).json({ message: '新增寶可夢成功!' });
} catch (error) {
console.error(error);
res.status(500).json({ error: '新增失敗' });
}
});
(備註小踩坑:libsql 的 args 不接受 undefined,如果是選填欄位,記得給個預設的 null 喔!)

- 驗證被擋下:在 Postman 不帶 Token 發送 POST,截圖被擋下 (401) 的畫面。

- 驗證通過並新增成功:在 Headers 帶上 Bearer Token 後,附上 JSON Body (包含 player_id, pokemon_id, gender 等) 發送,並截圖回傳
201 Created成功訊息的畫面。
我們成功實作了新增功能,而且不怕任何駭客的破壞!基本的 CRUD (增刪改查) 其實不難,但真正的挑戰在明天:我們要讓使用者上傳寶可夢的自訂圖示 (圖片檔案)!