接下來,我們要新增 Category 的 API,讓前端可直接讀取目前所有的分類、新增、修改、刪除分類。然後再調整讀取分類的那支 API,讓它一起回傳每個分類下的任務。
這樣,就完成 To-do List 的大致樣子了!
我們要新增 Category CRUD 的功能,API 的內容跟前面一開始建立 task API 的步驟一樣,如果忘記了或不確定可以參考 Day 11 ~ Day 14 天的文章,它有比較詳細的介紹。這邊我就分享 apiHandler.go
的 Categories 函式內容,剩下的 taskService.go
和 taskRepo.go
再依照程式邏輯自行完成~
// GET /categories
func GetCategories(c *gin.Context){
categories := taskService.GetCategories()
c.JSON(http.StatusOK, categories)
}
// POST /categories
func AddCatogories(c *gin.Context){
var input model.Category
err := c.ShouldBindJSON(&input)
if(err != nil){
c.Error(taskService.ErrInvalidInput)
return
}
newCategories, err := taskService.AddCategories(&input)
if err != nil {
c.Error(err)
return
}
c.JSON(http.StatusOK, newCategories)
}
// PATCH /categories/:id
func UpdateCategories(c *gin.Context) {
get_id := c.Param("id")
id, err := strconv.Atoi(get_id)
if err != nil {
c.Error(taskService.ErrInvalidID)
return
}
var input model.UpdateCategory
if err := c.ShouldBindJSON(&input); err != nil {
c.Error(taskService.ErrInvalidInput)
return
}
editCategory, err := taskService.UpdateCategories(id, input)
if err != nil{
c.Error(err)
return
}
c.JSON(http.StatusOK, editCategory)
}
// DELETE /categories/:id
func DeleteCategories(c *gin.Context){
get_id := c.Param("id")
id, err := strconv.Atoi(get_id)
if err != nil {
c.Error(taskService.ErrInvalidID)
return
}
err = taskService.DeleteCategories(id)
if err != nil{
c.Error(err)
return
}
c.JSON(http.StatusOK, gin.H{"message": "Delete successfully"})
}
完成 API 的函式之後,再來要更新 router
,把剛剛新增的 API 路徑更新到 main.go
檔案:
r.GET("/categories", apiHandler.GetCategories)
r.POST("/categories", apiHandler.AddCatogories)
r.PATCH("/categories/:id", apiHandler.UpdateCategories)
r.DELETE("/categories/:id", apiHandler.DeleteCategories)
這樣就設定完成了!接下來可以來測試看看 API~
完成上述 Category API 之後,輸入:go run main.go
,啟動 server 看看 API 回傳的內容。
這次我們使用 Postman 來測試 API!
Postman 是一套測試 API 非常好用的工具,模擬 HTTP 的各種請求方式,讓開發者可以快速的知道 API 的請求結果。
有興趣深入了解的大家可以參考以下資訊:
Postman 官網:https://www.postman.com/
Postman 教學:https://ithelp.ithome.com.tw/articles/10201503
以下是測試結果:
GET /categories
回傳:
[
{
"ID": 1,
"CreatedAt": "2025-10-06T00:05:49.595659+08:00",
"UpdatedAt": "2025-10-06T00:05:49.595659+08:00",
"DeletedAt": null,
"Name": "學習"
},
{
"ID": 2,
"CreatedAt": "2025-10-06T00:12:58.792427+08:00",
"UpdatedAt": "2025-10-06T14:07:31.832216+08:00",
"DeletedAt": null,
"Name": "工作"
}
]
POST /categories
輸入:
{
"Name": "運動"
}
回傳:
{
"ID": 2,
"CreatedAt": "2025-10-06T00:12:58.792427+08:00",
"UpdatedAt": "2025-10-06T00:12:58.792427+08:00",
"DeletedAt": null,
"Name": "運動",
"Tasks": null
}
PATCH /categories/:id
修改 id=2 的資料: /categories/2。
輸入:
{
"Name":"工作"
}
回傳:
{
"ID": 2,
"CreatedAt": "2025-10-06T00:12:58.792427+08:00",
"UpdatedAt": "2025-10-06T14:07:31.832216+08:00",
"DeletedAt": null,
"Name": "工作",
"Tasks": null
}
DELETE /categories/:id
刪除 id=3 的資料: /categories/3。
回傳:
{
"message": "Delete successfully"
}
上面就是 API 測試的結果~ 但是每一個回傳資料都有 CreatedAt
、UpdatedAt
、DeletedAt
這三個欄位,這是記錄在資料庫裡面的時間戳,可是前端不一定會需要這些資訊,所以後端就必須隱藏這些資料再傳給前端!
另外,我希望在讀取所有分類的時候,也可以同時看到每個類別底下的所有任務,這樣回傳給前端的時候,資料會更清楚~
綜合以上的需求,我需要修改的就是 taskModel.go
檔案裡面的資料結構!
隱藏 CreatedAt
、UpdatedAt
、DeletedAt
三個欄位:
把原本的 Task 與 Category 兩個結構裡面的 gorm.Model
移除,因為 gorm.Model
本身內建欄
位ID、CreatedAt、UpdatedAt、DeletedAt,所以這是原因!
那要怎麼隱藏呢?
→ 可以直接刪掉,然後定義需要的欄位即可!
type Task struct {
ID uint `json:"id" gorm:"primaryKey"`
Item string `json:"item"`
Status bool `json:"status"`
CategoryID uint
}
type Category struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `gorm:"not null;unique"`
}
新增每個分類的所有任務:
在 Category 資料結構裡新增 Tasks []Task
這一行,讓 Category 資料表連結 Task 資料:
type Category struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `gorm:"not null;unique"`
Tasks []Task `gorm:"foreignKey:CategoryID"` //與 Task 資料表連結
}
然後再到 taskRepo.go
檔案裡的 GetCategories()
函式中新增這一行程式:
database.DB.Preload("Tasks").Find(&categories)
Preload("Tasks")
是 GORM 的關鍵字,它會自動幫你把任務所屬的分類一起查出來。非常的方便!要記得 Preload()
引號裡的名稱要與 Category 結構裡的名稱定義一樣,不然會抓不到資料喔~完成修改之後,我們再重新 GET Category 的資料,就會看到每個分類底下的所有任務,而且時間戳也都隱藏了,這樣子資料就變得非常乾淨、整齊!
回傳:
[
{
"id": 1,
"Name": "學習",
"Tasks": [
{
"id": 5,
"item": "寫作業",
"status": false,
"CategoryID": 1
}
]
},
{
"id": 2,
"Name": "工作",
"Tasks": []
},
{
"id": 3,
"Name": "運動",
"Tasks": []
}
]