上一篇,我處理了前端表單、API 與資料庫欄位名稱不同的問題。
當欄位終於能正確對應後,新增寵物的資料也可以順利送到後端,並寫入資料庫。
但資料成功存進去之後,我很快就遇到下一個問題:
資料已經新增成功了,為什麼首頁還看不到?
當時的我原本以為,只要新增了什麼,畫面就會自然顯示什麼。
實際做過之後才發現,新增資料和顯示資料是兩段不同的流程。
資料成功進入資料庫,只代表新增完成;前端還需要主動更新畫面使用的資料,才能把內容顯示在畫面上。
第一次製作新增寵物功能時,我的想法很單純。
使用者填完表單,按下送出,資料成功寫進資料庫後,首頁應該就會直接出現新的寵物卡片。
在我的想像中,流程大概是:
填寫寵物資料
↓
按下新增
↓
資料進入資料庫
↓
首頁自動出現寵物
但真正實作後,我才發現中間少了一段重要的流程。
資料庫裡已經有資料,不代表前端畫面會自己知道。
前端仍然要主動更新目前使用的寵物清單,才能把資料顯示成登入後首頁 Dashboard 上的寵物卡片。
PawPal 的完整流程比較接近:
使用者填寫表單
↓
前端送出新增請求
↓
後端接收並處理資料
↓
資料寫入 PostgreSQL 的 pets 資料表
↓
後端回傳新增的寵物資料
↓
前端更新寵物清單
↓
把資料顯示成寵物卡片
Dashboard 第一次載入時會先取得寵物列表;新增成功後,則使用 POST 回傳的那筆資料更新前端清單,而不是再送一次 GET。
這也是我第一次比較明確地理解,Create 和 Read 雖然都在處理同一筆資料,但負責的是不同階段。
在學習 API 的過程中,我也開始接觸 Postman。
Postman 不是課堂上直接教我使用的工具,而是我在遇到 API 問題後,自己上網搜尋資料、跟著教學慢慢嘗試。
第一次打開 Postman 時,我其實幾乎看不懂。
畫面上有請求方法、網址、Headers、Body 和 Response,但我不知道每個區塊實際負責什麼,也還沒了解資料到底要放在哪裡。
一開始,我只知道把 API 網址貼進去,再按下送出。
後來使用幾次後,我才慢慢發現:
原來不一定要透過前端畫面,才能呼叫後端 API。
即使不透過前端表單,我仍然可以使用 Postman 直接送出請求,觀察後端回傳的結果。
這讓我第一次比較清楚地看見,前端與後端之間傳遞的資料實際長什麼樣子。
新增寵物時,PawPal 前端會把使用者填寫的資料送到 POST /api/v1/pets。
src/api/pet.js 使用 Axios。以下為依照實作整理的簡化概念,省略欄位轉換函式的細節:
const request = createPetRequestData(petData)
const response = await axios.post(
`${API_BASE_URL}/api/v1/pets`,
request.data,
{ headers: request.headers },
)
前端表單送出的資料包含 name、species、breed、gender、birthday、weight、microchipNumber、neutered、bloodType、furColor、note 與選取的 avatarFile。
送出前,src/api/pet.js 會把前端欄位轉成 API 使用的 snake_case,例如:
{
name,
species,
breed,
gender,
birthday,
weight,
microchip_number: microchipNumber,
neutered,
blood_type: bloodType,
fur_color: furColor,
notes: note,
}
如果使用者選了寵物照片,前端會改用 FormData,並把檔案放在 avatar 欄位;沒有檔案時才以 JSON 傳送。欄位轉換函式也支援將 photoUrl 對應為 avatar_url。
本文提到的新增與取得寵物 API,都需要登入狀態。前端會從 localStorage 讀取 pawpal_token,並在請求的 Authorization header 帶上 Bearer Token。
以前看到這段程式碼時,我只會把它理解成:
按下新增後,把資料送出去。
但透過 Postman 和實際測試後,我開始知道還要觀察更多事情。
例如:
只有看到畫面沒有報錯,還不能直接代表整段流程都正確。
測試新增寵物功能時,我不會只看前端畫面。
我通常會從三個地方確認。
第一個是查看 HTTP status code。
PawPal 成功建立寵物時,後端會回傳 201 Created。
它可以幫助我初步判斷,這次請求是否有成功被後端處理。
當時的我還記不住每一種狀態碼代表什麼,但至少開始知道:
PawPal 的驗證失敗會回傳 400,未登入或 Token 無效會回傳 401,晶片號碼重複則回傳 409;其他未預期的錯誤會以 500 回應。
第二個是查看後端回傳的 JSON。
建立成功時,後端回傳的結構是 { pet: ... },其中的寵物欄位仍使用資料庫端的 snake_case。以下為簡化概念:
{
"pet": {
"id": 42,
"user_id": 7,
"name": "小花",
"species": "dog",
"microchip_number": "123456789012345",
"neutered": false,
"avatar_url": "https://..."
}
}
Response 讓我可以看見後端實際回傳了哪些資料。
透過 Response,我才開始理解,API 不是只有把資料送出去,也會把處理結果傳回來。
前端的 src/api/pet.js 會把這個 pet 轉回前端使用的欄位名稱;若請求失敗,則統一回傳 success: false、訊息與後端 Response 資料,讓新增視窗與提示訊息可以顯示錯誤。
第三個是直接查看資料庫。
PawPal 使用 PostgreSQL 的 pets 資料表保存寵物資料。新增時,後端會以登入會員的 user_id 寫入 name、species、breed、gender、birthday、weight、microchip_number、neutered、blood_type、fur_color、notes 與 avatar_url。
pets.user_id 是外鍵,對應 users.id。因此,新增與取得寵物的 API 都會依登入者的 user ID 處理資料:新增時建立該會員的寵物,讀取時也只取得該會員的寵物。
如果 API 顯示成功,我還是會確認資料庫裡是否真的新增了一筆寵物資料。
這樣拆開檢查,也能幫助我判斷問題可能出在哪一段。
例如:
透過這些確認,我才不會只靠「看起來好像成功」判斷功能是否完成。
當需要取得已有的寵物資料時,PawPal 使用 GET /api/v1/pets。
src/api/pet.js 會從 Response 的 pets 陣列取出資料,再透過欄位轉換函式提供給前端。以下為簡化概念:
const response = await axios.get(`${API_BASE_URL}/api/v1/pets`, {
headers: getAuthHeaders(),
})
const pets = response.data.pets.map(mapPetFromApi)
GET 成功時會回傳 200 OK,Response 的結構是 { pets: [...] },不是直接回傳陣列。
GET 負責的不是建立新資料,而是取得已經存在的資料。
後端會用登入會員的 user ID 查詢 pets 資料表,再把資料透過 Response 回傳給前端。
前端取得資料後,才能把每一筆寵物資訊顯示成 Dashboard 上的卡片。
流程可以拆成:
Dashboard 載入時送出 GET 請求
↓
後端依登入會員查詢資料庫
↓
後端回傳 pets 陣列
↓
前端把 snake_case 欄位轉回前端格式
↓
畫面顯示寵物卡片
在這之前,我一直把「資料存在」和「畫面出現」當成同一件事。
真正做過後才發現,資料庫負責保存資料,前端畫面則需要透過 API 取得資料後才能顯示。
這次實作讓我印象最深的一件事,就是:
API 成功,不代表前端畫面會自己更新。
即使新增請求已經成功,資料也確實寫入資料庫,Dashboard 仍然可能維持原本的內容。
因為前端目前顯示的,可能還是新增前取得的那一份資料。
如果前端沒有重新取得最新資料,或沒有把新增結果加入畫面使用的資料清單,新的寵物卡片就不會自動出現。
PawPal 的實作採用第二種做法:Dashboard 透過 Pinia 的 petStore 建立寵物後,將 POST 回傳的資料正規化,再直接放進 pets 清單最前面。
以下同樣為依照實作整理的簡化概念:
const result = await createPetApi(payload)
const createdPet = normalizePet(result.data.pet)
pets.value = [createdPet, ...pets.value]
Vue 會依這個清單重新渲染寵物卡片,所以新增成功後不需要手動重新整理頁面。
這也是我第一次開始理解「畫面狀態」的重要性。
前端不會因為資料庫改變,就自動知道要重新顯示什麼。
必須由程式主動更新資料,畫面才會跟著改變。
當寵物資料從後端回到前端後,還需要把資料顯示在對應的位置。
在 PawPal 裡,使用者新增的寵物會出現在登入後首頁 Dashboard 的寵物卡片中。
Dashboard 從 petStore 的 pets 資料取得卡片內容,再交給 PetCard 元件顯示寵物名字和照片。
後端回傳的照片欄位是 avatar_url;前端的欄位轉換會把它轉成 photoUrl 與卡片使用的 image,因此照片可以正確顯示在卡片上。
以前我看到一張卡片,只會注意它的版面、文字和圖片。
完成這次功能後,我才知道一張卡片背後其實經過了很多步驟:
表單輸入
↓
POST 新增資料
↓
後端處理
↓
資料庫儲存
↓
POST 回傳新增資料並更新前端清單
↓
卡片渲染
畫面上只是一張寵物卡片,但背後連接了前端、API、後端和資料庫。
第一次看到自己的寵物真的出現在首頁卡片上時,我非常有成就感。
那不是隨便輸入的一筆測試資料,而是我自己養、也很喜歡的寵物。
從一開始建立表單、處理欄位,到後來串接 API、確認資料庫,再把資料顯示在首頁,整個過程花了我不少時間。
所以當那張卡片真的出現在畫面上時,我感受到的不是只有:
程式終於沒有報錯。
而是:
我真的從零做出了一個可以使用的功能。
原本只是一張空白的畫面,經過一步一步處理後,開始出現真實的資料。
這也是我第一次感受到,前端開發不只是把畫面切出來,而是讓使用者輸入的內容,真的能被保存、取得並顯示。
CRUD 通常代表四種資料操作:
但這篇所說的「完成」,不是四個功能都做完了,而是我第一次把新增與讀取真正串成一段完整流程。
也就是:
Create
新增寵物資料
↓
Read
取得並顯示寵物資料
在這個階段,我還沒有把 Update 和 Delete 全部完成。
但當 Create 和 Read 接起來後,這個功能才真正從「資料送得出去」,變成「使用者可以在畫面上看到結果」。
對當時的我來說,這已經是第一次完成一段完整的資料流程。
如果現在重新做一次,我會從一開始就把新增與顯示一起思考。
不會只問:
資料有沒有成功新增?
還會同時確認:
我也會更早使用 Postman 分開測試 API。
先確認後端可以正確處理資料,再處理前端畫面,可以讓我更容易知道問題是在前端、後端,還是資料庫。
完成新增與顯示流程後,我學到:
以前我把 CRUD 當成四個需要背起來的英文單字。
真正做過後才發現,它們代表的是使用者操作資料時,一段一段連接起來的流程。
而當自己的寵物第一次出現在首頁時,我也第一次真正感受到:
原來我真的可以把一個想法,做成畫面上可以使用的功能。
從新增到顯示,我第一次把 Create 和 Read 接成一段完整的資料流程。
但資料建立之後,使用者還會需要修改已經存在的內容。
下一篇,我會從第一次修改寵物資料開始,聊聊我為什麼做到這一步,才真正發現 PUT 和 PATCH 的差別。
Day 9|第一次修改資料,才知道 PUT 和 PATCH 差很多。