iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
自我挑戰組

Techschool Goalng Backend Master Class 的學習記錄系列 第 20

[Day 20] Implement RESTful in GO using Gin Part 3

  • 分享至 

  • xImage
  •  

Implement get account API


  • server.go 的NewServer中加入 getAccountrouter

    • Path包含我們想要獲取的Account的id:/accounts/:id
    server.go
    
    func NewServer(store *db.Store) *Server {
    	server := &Server{store: store}
    	router := gin.Default()
    
    	// TODO: add routes
      ...
      router.GET("/accounts/:id", server.getAccount)
    
    	server.router = router
    	return server
    }
    
  • 設定**getAccountRequest用来存儲輸入參數,其中有一個int64類型**的ID。

    • 因為ID是一個URI參數,我們不能像之前那樣從請求主體中獲取它。
    • 我們使用uri標籤告訴Gin URI參數的名稱。
    • 我們增加一個綁定條件,說明這個ID是必須的。
    type getAccountRequest struct {
    	ID int64 `uri:"id" binding:"required,min=1"`
    }
    
  • 在**server.getAccount處理器中,我們會像之前一樣操作。首先,我們宣告一個新的req變量,類型為getAccountRequest,並且調用ShouldBindUri 來取得ID**。

  • 如果出現ErrNoRows錯誤,我們返回400 Bad Request狀態碼,而預料之外的Error則向Client發送500 Internal Server Error ,反正成功GET為200 OK

type getAccountRequest struct {
	ID int64 `uri:"id" binding:"required,min=1"`
}

func (server *Server) getAccount(ctx *gin.Context) {
	var req getAccountRequest
	if err := ctx.ShouldBindUri(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, errorResponse(err))
		return
	}

	account, err := server.store.GetAccount(ctx, req.ID)
	if err != nil {
		if err == sql.ErrNoRows {
			ctx.JSON(http.StatusNotFound, errorResponse(err))
			return
		}
		ctx.JSON(http.StatusInternalServerError, errorResponse(err))
		return
	}

	ctx.JSON(http.StatusOK, account)
}

Test get account API with Postman

  • 重啟伺服器並開啟Postman測試。

  • 新增一個請求,方法選擇GET,URL設定為 http://localhost:8080/accounts/1

  • 結果成功,我們得到了200 OK狀態碼和找到的帳戶。

    https://ithelp.ithome.com.tw/upload/images/20231005/201217464u7tMQLGXl.png

  • 嘗試獲取不存在的帳戶,例如ID為100的帳戶。

  • 這次我們得到了404 Not Found狀態碼,錯誤:sql no rows in result set

    https://ithelp.ithome.com.tw/upload/images/20231005/20121746cKMs4752F4.png

  • 使用一個負的ID再次嘗試,例如-**1**

  • 我們得到了400 Bad Request狀態碼和關於驗證失敗的錯誤消息。

    https://ithelp.ithome.com.tw/upload/images/20231005/20121746RMTlenDb7s.png

Implement list account API


  • 隨著時間的推移,我們資料庫中儲存的帳戶數量可能會增長到一個非常大的數字。

  • 因此不應該在單一API呼叫中查詢和返回所有記錄,而是改為使用listAccount

  • 與**getAccount不同不再透過URI或是Body來取得參數,而是改用Query String。**

    • page_id: which is the index number of the page we want to get
    • page_size : which is the maximum number of records can be returned in 1 page
    http://localhost:8080/accounts?page_id=1&page_size=5
    
  • server.go 的NewServer中加入 listAccount router

    server.go
    
    func NewServer(store *db.Store) *Server {
    	server := &Server{store: store}
    	router := gin.Default()
    
    	// TODO: add routes
      ...
      router.GET("/accounts", server.listAccount)
    
    	server.router = router
    	return server
    }
    
  • 設定listAccountRequest用来存儲輸入參數,其中有一個**int64類型**的ID。

    • 這個結構應該存儲2個參數:PageIDPageSize
    • 我們不再從uri獲取這些參數,而是從Query String中獲取的,所以改為使用**form tag**。
    • 兩個參數都是必需的,**PageID**的最小值應該是1。
    • 對於**PageSize**,我們不希望它太大或太小,所以我設定其最小限制是5條記錄,最大限制是10條記錄。
    type listAccountRequest struct {
        PageID   int32 `form:"page_id" binding:"required,min=1"`
        PageSize int32 `form:"page_size" binding:"required,min=5,max=10"`
    }
    
  • 在**server.listAccount處理器中,我們會像之前一樣操作。首先,我們宣告一個新的req**變量,類型為listAccountRequest**,** 但在這會調用ShouldBindQuery來取得PageIDPageSize

  • 如果出現Query String錯誤,我們返回400 Bad Request狀態碼

  • 接下來調用**server.store.ListAccounts()從資料庫中查詢一頁的帳戶記錄,必須為其中的2個字段提供值:LimitOffset**。

    • Limit就是req.PageSize
    • 而**Offset是資料庫應該跳過的記錄數,因此我們必須使用這個公式來計算它:(req.PageID - 1) * req.PageSize**
  • **ListAccounts**如果發生錯誤,那麼我們則向Client發送500 Internal Server Error ,反之成功GET為200 OK

  • 在測試時嘗試獲取page_id=100page_size=5 的Accounts,會獲得 null response body,雖然這是可以接受的結果,但更好的結果是要returns an empty list

    • 接下來修改SQLC的參數emit_empty_slices:

      sqlc.yaml
      emit_empty_slices: true
      
      make sqlc
      

      https://ithelp.ithome.com.tw/upload/images/20231005/20121746WLx7ghIKSe.png

type getAccountRequest struct {
	ID int64 `uri:"id" binding:"required,min=1"`
}

func (server *Server) getAccount(ctx *gin.Context) {
	var req getAccountRequest
	if err := ctx.ShouldBindUri(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, errorResponse(err))
		return
	}

	account, err := server.store.GetAccount(ctx, req.ID)
	if err != nil {
		if err == sql.ErrNoRows {
			ctx.JSON(http.StatusNotFound, errorResponse(err))
			return
		}
		ctx.JSON(http.StatusInternalServerError, errorResponse(err))
		return
	}

	ctx.JSON(http.StatusOK, account)
}

Test list account API with Postman

  • 重啟伺服器並開啟Postman測試。

  • 新增一個請求,方法選擇GET,URL設定為 http://{{base_url}}/accounts?page_id=1&page_size=5

  • 結果成功,我們得到了200 OK狀態碼和找到的帳戶。

    https://ithelp.ithome.com.tw/upload/images/20231005/201217462nnpWnFDpv.png

  • 嘗試獲取page_id=100page_size=5 的Accounts,這次會獲得 null response body,雖然這是可以接受的結果,但更好的結果是要returns an empty list

    • 接下來修改SQLC的參數emit_empty_slices:

      sqlc.yaml
      emit_empty_slices: true
      
      make sqlc
      

      https://ithelp.ithome.com.tw/upload/images/20231005/20121746RVC3O2jd8C.png

  • 這次我們得到了404 Not Found狀態碼,錯誤:sql no rows in result set

    https://ithelp.ithome.com.tw/upload/images/20231005/20121746AjQcrBTYIB.png

  • 使用一個負的ID再次嘗試,例如-**1**

  • 我們得到了400 Bad Request狀態碼和關於驗證失敗的錯誤消息。

    https://ithelp.ithome.com.tw/upload/images/20231005/20121746NqWGmhks0U.png

  • 嘗試使用超出限制的page_size

    • 請求:更改**page_size**為20,這超出了最大限制10。

    • 回應:獲得了400 Bad Request的狀態碼。

    • 錯誤訊息:指出**page_size的驗證在max**標籤上失敗。

      https://ithelp.ithome.com.tw/upload/images/20231005/20121746TuUCC0DIIB.png

  • 嘗試使用page_id為0

    • 請求:設置**page_id**為0。

    • 回應:仍然獲得400 Bad Request的狀態碼。

    • 錯誤訊息:因為**page_id的驗證在required**標籤上失敗。這裡的情況是,在驗證器套件中,任何零值都會被認為是缺失的。在這種情況下這是可以接受的,因為我們無需設置第0頁。

      https://ithelp.ithome.com.tw/upload/images/20231005/20121746lzemphklD3.png


上一篇
[Day 19] Implement RESTful in GO using Gin Part 2
下一篇
[Day 21] Load Config in Go with viper
系列文
Techschool Goalng Backend Master Class 的學習記錄31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言