到了階段二,我們正式進入後端與資料層(Data Layer)的建立!
今天我們要為專案打造穩固的資料庫基石。我們將引導 AI 分析我們的產品需求,產出 ERD (實體關係圖) 概念,並自動生成 SQL Migration 腳本與現代 Node.js 絕佳搭配的 Drizzle ORM Schema。
在叫 AI 幫我們寫程式碼之前,先釐清兩個關鍵概念:
我們打開 IDE(如 Cursor / VS Code),在包含 .cursorrules 的環境下輸入以下 Prompt:
我們正在開發「AI 個人財務追蹤器」,請幫我設計 PostgreSQL 資料庫結構。
【需求】
1. 使用者 (Users) 系統
2. 交易記錄 (Transactions):包含金額、類型(收入/支出)、類別、日期、備註
3. AI 財務建議記錄 (AITips):記錄 AI 針對使用者財務狀況產生的分析建議
【產出要求】
- 先用 Mermaid 語法繪製 ERD 關係圖
- 使用 Drizzle ORM 語法定義 TypeScript Schema (包含 Primary Key, Foreign Key, Enum, CreatedAt)
AI 很快地生成了 Mermaid ERD 圖與以下 Drizzle Schema:
// schema.ts (AI 初次生成版本)
import { pgTable, text, timestamp, uuid, numeric, pgEnum } from 'drizzle-orm/pg-core';
export const transactionTypeEnum = pgEnum('transaction_type', ['INCOME', 'EXPENSE']);
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
});
export const transactions = pgTable('transactions', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id),
amount: numeric('amount').notNull(),
type: transactionTypeEnum('type').notNull(),
category: text('category').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
export const aiTips = pgTable('ai_tips', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id),
content: text('content').notNull(),
});
看似很完美,但我仔細檢查後發現了 兩個潛在的坑 (Technical Debt & Bugs):
userId: uuid('user_id').references(() => users.id) 沒有設定 onDelete: 'cascade'。如果未來刪除使用者,資料庫會因為外鍵約束跳錯而無法刪除,或者殘留孤立的交易紀錄。numeric('amount') 在 JavaScript/TypeScript 中預設會被映射為 string(為了避免 JS 浮點數精度不準的問題)。但後端做計算時,如果不注意直接拿來做加減,會變成字串串接 (例如 '100' + '50' = '10050')!我針對問題給出第二輪修正指令:
請修正上面的 Drizzle Schema:
1. 為所有 foreign key 加上 `onDelete: 'cascade'`。
2. 在 transactions 表中加上 `date` 欄位(預設為當前時間)。
3. 導出 Drizzle Migration 腳本指令,並提醒我 numeric 型別在 JavaScript 中運算時的注意事項。
// schema.ts (最終優化版)
import { pgTable, text, timestamp, uuid, numeric, pgEnum } from 'drizzle-orm/pg-core';
export const transactionTypeEnum = pgEnum('transaction_type', ['INCOME', 'EXPENSE']);
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const transactions = pgTable('transactions', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
amount: numeric('amount', { precision: 12, scale: 2 }).notNull(),
type: transactionTypeEnum('type').notNull(),
category: text('category').notNull(),
date: timestamp('date').defaultNow().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const aiTips = pgTable('ai_tips', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
content: text('content').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
接著執行 Drizzle Kit 生成並推送 Migration:
npx drizzle-kit generate
npx drizzle-kit push
將精力從「手寫語法」轉移到「架構審查與業務邏輯驗證」!
今日雖然有讓 AI生出 80% 的資料庫結構,但身為開發者,我們必須發揮人類工程師的價值:檢查邊界條件、維護資料完整性 (Integrity),以及預防 JavaScript 轉型(Type Coercion)的潛在漏洞!