到了 Day 9,我們的「AI 個人財務追蹤器」已經擁有 Auth 機制與 API。但大部分 AI 預設產出的程式碼都有個致命缺點:極度樂觀。
預設狀況下,AI 往往只考慮使用者「輸入正確金額、正確日期」的快樂路徑 (Happy Path)。只要前端傳來 -9999 金額、未定義的 category,或是帶有 HTML 標籤的防護攻擊字串,伺服器就會直接爆掉(回傳 500 Internal Server Error),甚至寫入污染資料!
今天我們要開啟 Vibe Mode,引導 AI 為 API 加上 Zod 強型別 Schema 驗證 與 全域 Try-Catch / Exception Filter。
我們正在開發 Next.js 14 App Router 專案中的「新增交易紀錄 API」(/api/transactions)。
請幫我撰寫 API Route Handlers (POST 方法):
1. 使用 Zod 驗證前端傳入的 Request Body。
2. 欄位包含:amount (正數數值)、type ('income' | 'expense')、category (字串)、date (ISO 日期格式)。
3. 加入完整的 Try-Catch 與錯誤處理,當驗證失敗時回傳 400,伺服器異常回傳 500。
2. AI 產出的結果 (Code Snippet)
AI 很快產出了以下程式碼:
// app/api/transactions/route.ts
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { createClient } from '@/utils/supabase/server'
const transactionSchema = z.object({
amount: z.number().positive(),
type: z.enum(['income', 'expense']),
category: z.string(),
date: z.string(),
})
export async function POST(request: Request) {
try {
const body = await request.json()
const validatedData = transactionSchema.parse(body)
const supabase = createClient()
const { data, error } = await supabase
.from('transactions')
.insert(validatedData)
if (error) throw error
return NextResponse.json({ success: true, data }, { status: 201 })
} catch (error) {
return NextResponse.json({ error: (error as Error).message }, { status: 400 })
}
}
雖然這段程式碼看似有用了 Zod 和 Try-Catch,但有三個防禦性漏洞:
* 混淆 400 與 500 錯誤:在 catch 區塊中,AI 將「所有」捕捉到的錯誤(不論是 Zod 驗證失敗還是 Supabase 連線中斷)通通統一回傳 status: 400。資料庫斷線應該是 500 Server Error,而不是用戶的錯!
* 型別強制轉換漏洞:前端表單傳送過來的金額有時是字串 "150",z.number().positive() 會直接噴錯,體驗極差。應該使用 z.coerce.number() 進行彈性轉型。
* 敏感資訊外洩:如果 Supabase 噴出資料庫內部錯誤訊息(例如 SQL constraint 衝突),直接暴露給前端會造成資安漏洞。
💥 翻車與除錯過程 (Debug Experience)
🐛 踩坑:z.parse() 直接拋出 Unhandled Exception 導致日誌失控
當前端傳入不合規的資料(例如空物件 {})時,transactionSchema.parse() 會拋出 ZodError。因為 AI 寫的程式碼直接把它轉成字串暴露給前端:
{
"error": "[\n {\n \"code\": \"invalid_type\",\n \"expected\": \"number\",\n \"received\": \"undefined\",\n \"path\": [\n \"amount\"\n ],\n \"message\": \"Required\"\n }\n]"
}
前端收到這一長串 Raw JSON 字串完全無法直接呈現給 User,且伺服器 Console 被滿滿的預期內驗證錯誤洗版!
我的引導修正 (Secondary Prompt)
我把這個回應結構丟回給 AI,要求重構 Zod 的驗證邏輯與錯誤轉化器:
你產出的 catch 區塊問題很大:
最終修復後的 API Handler
// app/api/transactions/route.ts
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { createClient } from '@/utils/supabase/server'
const transactionSchema = z.object({
amount: z.coerce.number().positive('金額必須大於 0'),
type: z.enum(['income', 'expense'], { errorMap: () => ({ message: '類型必須為收入或支出' }) }),
category: z.string().min(1, '請選擇分類'),
date: z.string().datetime({ message: '無效的 ISO 日期格式' }),
})
export async function POST(request: Request) {
try {
const body = await request.json()
// 1. 使用 safeParse 避免拋出例外,優化控制流
const validationResult = transactionSchema.safeParse(body)
if (!validationResult.success) {
// 提取並格式化欄位錯誤訊息
const fieldErrors = validationResult.error.flatten().fieldErrors
return NextResponse.json(
{ success: false, errors: fieldErrors },
{ status: 400 }
)
}
const supabase = createClient()
const { data, error } = await supabase
.from('transactions')
.insert(validationResult.data)
.select()
if (error) {
console.error('[Database Error]:', error)
return NextResponse.json(
{ success: false, message: '資料庫寫入失敗' },
{ status: 500 }
)
}
return NextResponse.json({ success: true, data }, { status: 201 })
} catch (err) {
console.error('[Unhandled API Error]:', err)
return NextResponse.json(
{ success: false, message: '伺服器內部發生未知錯誤' },
{ status: 500 }
)
}
}
Vibe Coding 的最高境界不是程式碼寫得多快,而是你能不能預先看見 AI 的盲點。
寫出 Happy Path 只需要 10 秒,但加入嚴密的 Validation 與 Try-Catch 才決定了你的專案是一張一撕就破的紙,還座堅固的堡壘。
明天 Day 10,我們要運用 AI 幫我們自動編寫 API 單元測試與整合測試 (Vitest/Jest),驗證這些邊界條件是否真的萬無一失!