正式開始製作 PawPal 後,我第一個接手的任務,是寵物基本資料相關功能。
我接手時,Dashboard 已經使用假資料呈現寵物卡片。之後我負責建立 pets 資料表與寵物 CRUD API,並把既有的新增按鈕與寵物基本資料彈窗串接真實資料,讓資料能寫入後端並更新 Dashboard。
原本以為只要把既有畫面接上真實資料,再把內容放進對應的位置,這個功能應該不會太困難。
但真正開始做之後,我才發現,一張看起來簡單的寵物卡片,背後其實牽涉到很多事情:
這也是我第一次感受到:
畫面上看到的只是一張卡片,但一個完整功能遠遠不只有畫面。
剛開始接手寵物區塊時,Dashboard 上的寵物卡片先使用假資料確認畫面。
概念大概像這樣:
const pet = {
name: 'Milk',
species: '貓',
breed: '英國短毛貓',
gender: 'male',
birthday: '2022-05-10',
}
使用假資料的好處,是可以先專心處理前端畫面。
這時候不用等待 API,也不用先處理資料庫,只要確認:
當時的我以為,畫面完成後,接下來只要把假資料換成 API 資料就好了。
後來才發現,事情沒有這麼簡單。
當功能準備從假資料走向真實資料時,第一個要處理的問題不是寫 API,而是先決定:
一隻寵物到底需要保存哪些資料?
我們和有養寵物經驗的組員一起討論,整理出飼主實際需要記錄的內容。
除了名字、種類和品種之外,還包含:
這時我才發現,資料欄位不是想到什麼就直接加什麼。
每個欄位都還要進一步思考:
例如寵物名字通常會是文字:
name: 'Milk'
體重會比較適合使用數字:
weight: 5.2
結紮狀態則可以使用布林值:
neutered: true
生日則需要考慮日期格式:
birthday: '2022-05-10'
以前練習 JavaScript 時,我知道字串、數字和布林值是不同的資料型別。
但直到真正規劃功能時,我才開始理解:
資料型別不是考試中的名詞,而是會直接影響資料怎麼保存、驗證和使用。
原本我接手的是寵物基本資料功能,最先接觸到的是 Dashboard 上既有的寵物卡片與相關畫面。
但當我們開始討論資料從哪裡來時,工作很自然地一路延伸到資料庫和 API。
因為前端想顯示寵物資料,後端就必須能提供資料;後端要提供資料,資料庫裡就必須先有正確的資料表。
整個流程變成:
資料庫建立 pets 資料表
↓
後端建立寵物 API
↓
前端呼叫 API
↓
取得寵物資料
↓
顯示在寵物卡片上
這是我第一次不是只站在前端畫面的角度看功能。
以前我比較容易覺得,前端只要把畫面做出來就完成了。
但這次我開始理解,真實資料要出現在畫面上,必須經過資料庫、後端 API,再回到前端。
少了任何一段,功能都不完整。
pets 資料表在規劃完欄位後,我開始建立 pets 資料表。
以下是依照當時欄位整理後的簡化範例:
CREATE TABLE pets (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
name VARCHAR(100) NOT NULL,
species VARCHAR(50) NOT NULL,
breed VARCHAR(100),
gender VARCHAR(20),
birthday DATE,
weight NUMERIC(5,2) CHECK (weight >= 0),
microchip_number VARCHAR(50) UNIQUE,
blood_type VARCHAR(20),
fur_color VARCHAR(100),
neutered BOOLEAN NOT NULL DEFAULT FALSE,
avatar_url TEXT,
notes TEXT
);
這段程式碼不是要一次背起來。
當時對我來說,真正重要的是開始理解每個欄位代表什麼。
例如:
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY
代表每一隻寵物都會有自己的唯一 ID,而且新增資料時,資料庫會自動產生編號。
name VARCHAR(100) NOT NULL
代表名字是文字,而且不能沒有名字。
birthday DATE
代表生日要使用日期型別。
neutered BOOLEAN NOT NULL DEFAULT FALSE
代表結紮狀態只有兩種主要狀態,可以使用 true 或 false。
建立資料表時,我也需要使用 AI 協助確認 SQL 語法與欄位型別。
但 AI 給出的建議不能直接全部照抄。
我仍然需要確認:
這讓我開始理解,AI 可以幫助我整理與檢查,但資料結構最後仍然要由我們根據功能需求決定。
user_id?在 pets 資料表中,有一個當時讓我印象很深的欄位:
user_id INTEGER NOT NULL REFERENCES users(id)
一開始我只知道這個欄位和會員有關。
後來才逐漸理解,每一隻寵物都必須知道自己屬於哪一位會員。
假設資料庫裡有很多會員,也有很多寵物:
會員 1
├── Milk
└── Coco
會員 2
├── Lucky
└── Momo
user_id 的外鍵負責保存這筆寵物資料屬於哪一位會員。不過,外鍵本身只定義資料庫的關聯,並不會自動完成 API 的權限控制。
在 PawPal 中,查詢、修改與刪除寵物時,後端會先透過 JWT 驗證取得目前登入會員的 req.userId,再把它放進查詢條件。這樣才能限制會員只能操作自己的寵物資料。
也就是說,user_id 負責資料歸屬;JWT 驗證和後端查詢條件,則負責限制誰可以查詢或操作資料。
這時我第一次比較具體地理解資料表之間的關係:
users
↓
一位會員可以擁有多隻寵物
↓
pets
以前學習資料庫時,「關聯」對我來說只是名詞。
真正做到寵物功能後,我才知道,資料關聯和 API 權限控管需要分開理解,但兩者會一起影響使用者能看到和操作哪些資料。
資料表建立後,接著要透過後端 API 讓前端取得寵物資料。
例如前端想取得目前會員的寵物列表,就需要呼叫一支查詢 API。
在 PawPal 中,列表查詢路徑是:
GET /api/v1/pets
前端發出請求後,後端會:
接收請求
↓
確認目前登入會員
↓
從 pets 資料表查詢資料
↓
將結果整理成 JSON
↓
回傳給前端
前端收到資料後,再把內容顯示成寵物卡片。
這讓我開始理解,API 回傳的資料並不是自己出現在畫面上。
它的背後有完整流程:
PostgreSQL
↓
Node.js / Express
↓
API Response
↓
Axios
↓
Vue
↓
寵物卡片
當時我不一定能完整說出每一層的責任,但我開始知道,遇到資料沒有顯示時,不能只看前端畫面。
問題會出現在資料庫、後端、API,或前端接收資料的任何一個位置。
在前端正式串接之前,我們先使用 Postman 測試後端 API。
Postman 可以幫助我們直接向後端發送請求,不需要先透過前端畫面。
例如測試查詢寵物資料時,可以確認:
如果 Postman 已經能正確取得資料,代表這次請求經過的後端 API 與資料庫查詢流程基本上可以正常運作。
接著再處理前端串接時,排查範圍就會比較小。
這也是我第一次開始建立「分段測試」的概念。
不是等所有東西都完成後,再一次測整個功能,而是:
先確認資料庫
↓
再確認 API
↓
最後確認前端畫面
第一次使用 Supabase 查看資料時,也發生了一個讓我印象很深的插曲。
當時我在 Postman 新增或測試完資料後,切回 Supabase 查看 pets 資料表。
但重新整理畫面時,我一度以為剛剛建立的資料消失了。
看到畫面沒有按照預期顯示時,我很緊張,開始懷疑:
後來重新確認後,才發現資料並沒有真的消失,只是當時對 Supabase 介面和更新方式還不熟悉。
現在回頭看,這只是一個很小的操作問題。
但對第一次接觸資料庫管理介面的我來說,畫面上的任何變化都很容易讓我以為自己把資料弄壞了。
這次經驗也讓我開始養成習慣:
看到資料不見時,不要立刻判斷資料真的被刪除了,而是先重新查詢、確認資料表與 API 回應。
當資料表和 API 準備完成後,下一步就是把寵物卡片裡的假資料換成 API 回傳的真實資料。
原本是直接寫在前端:
const pet = {
name: 'Milk',
species: '貓',
}
串接後,資料則需要透過 API 取得。以下為省略 API_BASE_URL、共用 API 函式與欄位轉換流程後的簡化範例:
const response = await axios.get('/api/v1/pets', {
headers: { Authorization: `Bearer ${token}` },
})
const pets = response.data.pets
實際專案會在 src/api/pet.js 透過 listPets() 讀取 pawpal_token、附上 Bearer Token,並使用 mapPetFromApi 將 response.data.pets 轉成前端使用的欄位格式。
接著再將取得的資料顯示在畫面上。
當我第一次看到資料庫裡的寵物資料,成功出現在前端寵物卡片上時,我真的很有成就感。
因為那不再只是我手動寫進 JavaScript 的假資料。
而是一筆真正經過:
資料庫
↓
後端 API
↓
前端請求
↓
畫面顯示
的資料。
那一刻我第一次比較清楚地感受到,前端、後端和資料庫不是三個完全分開的東西。
它們是在同一個功能裡一起合作。
雖然寵物資料成功出現在畫面上,但後來我們也發現,有些欄位沒有按照預期顯示或處理。
當時我一度以為是不是 API 邏輯寫錯,或資料庫沒有正確回傳。
找了很久後才發現,前端、後端與資料庫使用的欄位名稱並沒有完全一致。
例如前端使用:
microchipNumber
但後端和資料庫使用的是:
microchip_number
對人來說,兩個名稱都在表示晶片號碼。
但對程式來說,它們是兩個不同的欄位。
這個問題後來成為我串接 API 時非常深刻的一次經驗。
下一篇會再完整整理,欄位名稱沒有統一時,資料為什麼會對不上,以及我們最後怎麼集中處理轉換。
在接手寵物功能以前,我對前端任務的理解比較接近:
看到設計稿
↓
把畫面切出來
↓
加上互動
↓
完成
但這次真正走過一次後,我才發現,一個功能包含:
確認使用者需求
↓
規劃資料欄位
↓
選擇資料型別
↓
建立資料表
↓
建立 API
↓
測試資料
↓
前端串接
↓
顯示畫面
↓
處理錯誤
原本只是一張寵物卡片,最後卻讓我一路接觸到資料庫、API、會員關聯和前後端串接。
這個過程並沒有想像中順利。
很多時候,我只是先把眼前的問題處理好,再慢慢理解每一段程式和流程為什麼存在。
但也正是因為這個功能,我第一次真正看見一筆資料是怎麼從資料庫走到畫面上的。
第一次接手寵物功能,讓我學到幾件事:
user_id 和會員建立關聯。這次真正讓我有成就感的,不只是完成一張寵物卡片。
而是第一次看到自己參與建立的資料,從資料庫經過後端 API,最後真的出現在前端畫面上。
對現在的我來說,這也許只是前後端串接的基本流程。
但對當時剛開始接觸專案的我來說,這是第一次覺得:
原來我真的可以把一個功能,從資料一路做到畫面上。
寵物資料成功出現在畫面後,我原本以為最困難的部分已經結束了。
但接著我才發現,前端、API 和資料庫只要有一個欄位名稱沒有對上,資料就無法按照預期顯示。
下一篇:
Day 7|表單、API 與資料庫:欄位沒有統一,資料就對不上