理解 Gin 框架的 API 實作之後,今天就要來完成 To-do List 的「 新增 、讀取、更新、 刪除 」 (CRUD:Create、Read、Update、Delete)功能!
先來介紹為什麼要做 To-do List 好了~ 😆
是這樣的,當初在想專案要做什麼的時候,希望是不要太複雜,但是又可以實作後端很常用到的功能,所以就想到可以做一個類似備忘錄的小工具!這樣 CRUD 通通有用到,哈哈 🤣
To-do List 小工具功能與初步規劃:
/tasks
)。/tasks
)。/task/:id
)。/task/:id
)。上面這四個功能就是所有的 API 了!作法一樣是會先一個一個把各自的 function 寫好,然後透過 main fuction 呼叫。
那我們就開始吧!
先建立 Task struct 和「 放任務的地方 」:
// 完成 Task struct 和 放 task 的地方 []
type Task struct { // Task 結構
ID int `json:"id"`
Item string `json:"item"`
Status bool `json:"status"`
}
var tasks = []Task{ // 放置所有任務的空間
{ID: 1, Title: "範例一", Status: false},
{ID: 2, Title: "範例二", Status: false},
}
再來,就是把新增、讀取、更新、刪除任務的 function 完成:
Create:新增任務 (POST /tasks
)。
// POST /tasks
func addTask(c *gin.Context) {
var newTask Task
if err := c.ShouldBindJSON(&newTask); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
newTask.ID = len(tasks) + 1
newTask.Status = false
tasks = append(tasks, newTask)
c.JSON(http.StatusOK, newTask)
}
Read:查詢所有任務 (GET /tasks
)。
func getTasks(c *gin.Context) {
c.JSON(http.StatusOK, tasks)
}
Update:更新任務狀態 (PATCH /task/:id
)。
// PATCH /task/:id
func updateTask(c *gin.Context) {
get_id := c.Param("id")
fmt.Println("id:", get_id)
id, err := strconv.Atoi(get_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
var input struct {
Item *string `json:"item"`
Status *bool `json:"status"`
}
fmt.Println("input:", input)
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
for i, t := range tasks {
if t.ID == id {
if input.Item != nil {
tasks[i].Item = *input.Item
}
if input.Status != nil {
tasks[i].Status = *input.Status
}
c.JSON(http.StatusOK, tasks[i])
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
}
Delete:刪除任務 (DELETE /task/:id
)。
// DELETE /task/:id
func deleteTask(c *gin.Context) {
get_id := c.Param("id")
fmt.Println("id:", get_id)
id, err := strconv.Atoi(get_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
for i, t := range tasks {
if t.ID == id {
tasks = append(tasks[:i], tasks[i+1:]...)
c.JSON(http.StatusOK, gin.H{"message": "Delete successfully"})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
}
完整 main.go
內容:
//完整 main.go
package main
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type Task struct {
ID int `json:"id"`
Item string `json:"item"`
Status bool `json:"status"`
}
var tasks = []Task{
{ID: 1, Item: "鐵人賽文章", Status: false},
}
func main() {
r := gin.Default()
r.GET("/tasks", getTasks)
r.POST("/tasks", addTask)
r.PATCH("/task/:id", updateTask)
r.DELETE("/task/:id", deleteTask)
r.Run(":8080")
}
// GET /tasks
func getTasks(c *gin.Context) {
c.JSON(http.StatusOK, tasks)
}
// POST /tasks
func addTask(c *gin.Context) {
var newTask Task
if err := c.ShouldBindJSON(&newTask); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
newTask.ID = len(tasks) + 1
newTask.Status = false
tasks = append(tasks, newTask)
c.JSON(http.StatusOK, newTask)
}
// PATCH /task/:id
func updateTask(c *gin.Context) {
get_id := c.Param("id")
fmt.Println("id:", get_id)
id, err := strconv.Atoi(get_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
var input struct {
Item *string `json:"item"`
Status *bool `json:"status"`
}
fmt.Println("input:", input)
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
for i, t := range tasks {
if t.ID == id {
if input.Item != nil {
tasks[i].Item = *input.Item
}
if input.Status != nil {
tasks[i].Status = *input.Status
}
c.JSON(http.StatusOK, tasks[i])
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
}
// DELETE /task/:id
func deleteTask(c *gin.Context) {
get_id := c.Param("id")
fmt.Println("id:", get_id)
id, err := strconv.Atoi(get_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
for i, t := range tasks {
if t.ID == id {
tasks = append(tasks[:i], tasks[i+1:]...)
c.JSON(http.StatusOK, gin.H{"message": "Delete successfully"})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
}
完成之後,就來測試 API 吧!
我們一樣使用 終端機(cmd)來試打 API,看看它的回傳結果以及功能是不是沒有問題的。
要記得先開啟 server 才有辦法測試喔~
查詢任務(GET /tasks
)
curl http://localhost:8080/tasks
回傳:
新增任務(POST /tasks
)
curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"item":"參加 IT 鐵人賽"}'
回傳:
修改任務(PATCH /task/:id
)
curl -X PATCH http://localhost:8080/task/1 \
-H "Content-Type: application/json" \
-d '{"status": true}'
回傳:
這時候會發現 id:1 的 status 已經被我們改成 true 了!
刪除任務(DELETE /task/:id
)
curl -X DELETE http://localhost:8080/tasks/2
回傳:
這樣就全部的功能都有測試過一次了,算是完成基本 To-do List 初版了!!!🎉🎉🎉
是不是沒有想像中的困難呢~
明天會介紹 RESTful API 的設計原則 ✨ 再來看看我們今天設計的 API 有沒有符合設計規範~