มาเขียน RESTful API ง่ายๆ ด้วย gin กันเถอะ

มาเขียน RESTful API ง่ายๆ ด้วย gin กันเถอะ
30/03/22   |   1.8k   |  

   ช่วงนี้ community ของภาษา Go เริ่มมีเยอะขึ้นและมีการใช้อย่างแพร่หลาย เลยเกิดความสนใจในตัวภาษา จึงเริ่มศึกษาก็เกิดความหลงไหลในตัวภาษานี้ เลยตั้งใจลองทำตัว RESTful API ก็ได้ไปเจอ gin framework ที่เป็นที่นิยมของชุมชนของ Go developer ที่จะสามารถช่วยในการทำ RESTful ได้ง่ายและรวดเร็ว

  รายการที่เราจะทำในบทความนี้

  1. Design API endpoints.
  2. Initiation Project
  3. สร้างข้อมูลตัวอย่าง
  4. เขียนตัวจัดการและคืนค่าข้อมูลทุกตัว
  5. เขียนตัวจัดการและคืนค่าข้อมูลตัวที่เราสนใจ
  6. เขียนตัวจัดการและเพิ่มข้อมูล
  7. เขียนตัวจัดการและแก้ไขข้อมูล
  8. เขียนตัวจัดการและลบข้อมูล

 

  ก่อนจะเริ่มเราต้องมีอะไรบ้าง

  1. install go แนะนำ version ตั้งแต่ 1.16 หรือมากกว่า install go
  2. tool to edit สำหรับการเขียนโค้ดแนะนำ VScode
  3. เครื่องมือในการทดสอบ API แนะนำ Postman

 

  Design API endpoints

   เราจะสร้าง API ที่ให้สิทธิ์เข้าถึงสำหรับร้านหนังสือ โดยจะต้องสร้างปลายทางที่ user สามารถอ่านเพิ่มหรือลบของรายการหนังสือได้ โดยรูปแบบข้อมูลจะมี id ของหนังสือ, ชื่อหนังสือ, ผู้แต่งหนังสือ และ ราคาหนังสือ โดย route ที่เราจะสร้างมี

/books
​   [GET]: แสดงหนังสือทั้งหมดโดย return กลับมาเป็น JSON
   [POST]: เพิ่มหนังสือโดย return กลับมาเป็น JSON
/book/:id
   [GET]: แสดงหนังสือตาม id
   [PUT]: อัพเดทรายละเอียดของหนังสือตาม id หนังสือ return กลับมาเป็น JSON
   [DELETE]: ลบหนังสือตาม id ที่ส่งมา return กลับมาเป็นข้อความ 
             "delete success"

 

  Initiation Project

  1. เปิด command prompt และเปลี่ยนไปที่ home directory หรือ directory ที่ต้องการจัดเก็บ folder
    On Linux or Mac: $ cd
    On Windows: C:\> cd %HOMEPATH%
  2. ใช้ command สร้าง folder
    $ mkdir web-service-gin
    $ cd web-service-gin
  3. สร้าง module สำหรับจัดการ dependencies สำหรับกำหนด part ของโค้ดเมื่อ run command นี้เสร็จจะได้ ไฟล์ที่ชื่อ go.mod
    $ go mod init example/web-service-gin
  4. สร้าง file หลักของ project นี้
    $ touch main.go
  5. install gin framwork สำหรับทำ RESTful API
    $ go get -u github.com/gin-gonic/gin
  • เมื่อ run command นี้เสร็จจะได้ไฟล์ go.sum เพิ่มขึ้นมาโดยไฟล์นี้จะทำการเก็บ cryptographic เพื่อสำหรับการ checksum โดยมันจะมีไว้เพื่อตรวจว่าไฟล์ที่เราโหลดมาถูกต้องตามต้นฉบับหรือเปล่า

    6. รูปแบบ tree จะเป็นลักษณะนี้

web-service-gin
|-go.mod
|-main.go
|-go.sum

 

  สร้างข้อมูลตัวอย่าง

  1. เราจะโค้ดใน file ที่ชื่อว่า main.go
  2. ด้านบนสุดของไฟล์จะประกาศชื่อ package
    package main
  3. ใต้ package ให้วางโครงสร้างของหนังสือ โดยประกาศเป็น struct
//รูปแบบของข้อมูลหนังสือ
type book struct {
  ID string `json:"id"`
  Name string `json:"name"`
  Author string  `json:"author"`
  Price  float64 `json:"price"`
}

   4. หลังจากวางโครงสร้างเสร็จให้ทำการประกาศ data ของหนังสือเป็น slice สำหรับทำการ mock รายการหนังสือ

// รายการของหนังสือที่มีทั้งหมด
var books= []book{
    {
      ID: "1", 
      Name: "Harry Potter", 
      Author: "J.K. Rowling", 
      Price: 15.9
    },
    {
      ID: "2", 
      Name: "One Piece", 
      Author: "Oda Eiichirō", 
      Price: 2.99
     },
    { 
      ID: "3", 
      Name: "demon slayer", 
      Author: "koyoharu gotouge", 
      Price: 2.99
    },
}

 

  เขียนตัวจัดการและคืนค่าข้อมูลทุกตัว

  เมื่อผู้ใช้ request GET /booksแล้วสิ่งที่ผู้ใช้ต้องได้คือรายการหนังสือทั้งหมดมาเป็นรูปแบบไฟล์ JSON ดังนั้นเราจะต้องทำ

  • ทำ logic สำหรับ return รายการหนังสือทั้งหมด
  • จับคู่ระหว่าง request ที่เรียกมากับ logic

  เริ่มโดย

  1. ทำการเพิ่มฟังก์ชั่น getBooks โดย ฟังก์ชั่นนี้สร้างหลังจากประกาศ slice ของ books
func getBooks(c *gin.Context) {
    c.JSON(http.StatusOK, books)
}

  ในโค้ดข้างต้นนี้:

  • func getBooks เป็นฟังก์ชั่นเขียนในรูป func และชื่อฟังก์ชั่น getBooks โดยรับ parameter เป็น gin.Context
  • gin.Context เป็นส่วนสำคัญที่สุดของ gin มีรายละเอียดของ request, validates, จัดรูปแบบเป็น JSON เป็นต้น
  • Context.JSON ทำให้ struct เป็นรูปแบบ JSON และ response และตั้งค่า Content-Type เป็น application/json
    ในฟังก์ชั่น argument แรกคือ HTTP status code ที่จะส่งกลับไปหา client
    จาก code http.StatusOK เป็นค่าคงที่ของ package net/http มีค่าเป็น 200
    ในฟังก์ชั่น argument ตัวถัดไปคือ slice ของรายการหนังสือทั้งหมดที่จะตอบกลับไป

    2. ทำการสร้าง ฟังก์ชั่น main ให้อยู่ถัดไปจากประกาศ slice books

func main() {
    router := gin.Default()
    router.GET("/books", getBooks)

    router.Run("localhost:8080")
}

  ในโค้ดข้างต้นนี้:

  • เริ่มต้นจากตั้งค่า gin router ให้ใช้ค่า Default
  • ใช้ฟังก์ชัน GET เพื่อเชื่อมโยงเมธอด GET HTTP และ route /books กับฟังก์ชันgetBooks ที่เป็นตัวจัดการคืนค่ารายการหนังสือทั้งหมด
  • ใช้ฟังก์ชัน Run เพื่อเชื่อมต่อเราเตอร์กับ http.Server และเริ่มเซิร์ฟเวอร์

   3.ทำการ import package ในการใช้งานของโค้ดชุดนี้โดยให้อยู่ดัดไปจากประกาศตัว package main

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

  ในโค้ดข้างต้นนี้:

  • เริ่มต้นจากตั้งค่า gin router ให้ใช้ค่า Default
  • net/http ใช้สำหรับเรียกค่า constatn ของ status code
  • github.com/gin-gonic/gin ใช้ในการจัดการ server, route, response เป็นต้น

   4. ทำการ save ไฟล์ main.go ทำการ Run ด้วย command go run main.go
เมื่อโค้ดทำงานจะมี HTTP server ทำงานอยู่ตอนนี้เราสามารถ send requests ได้แล้ว

ทำการ requests web service ที่เราเขียนไว้

การทดสอบ API ในที่นี้ขอใช้ Post man เป็นการทดสอบ API

  1. ใส่ url localhost:8080/booksใช้เมธอด GET send request
  2. เมื่อกด send จะได้รายการหนังสือทั้งหมดมาเป็น Array of object พร้อม status 200

ผลลัพธ์การเรียก API[GET] /books

 

  เขียนตัวจัดการและคืนค่าข้อมูลตัวที่เราสนใจ

   เมื่อผู้ใช้ request GET /book/:idแล้วสิ่งที่ผู้ใช้ต้องได้คือรายการหนังสือตาม id เป็นรูปแบบไฟล์ JSON ดังนั้นเราจะต้องทำ

  • ทำ logic สำหรับ return รายการหนังสือตาม paramiter id
  • จับคู่ระหว่าง request ที่เรียกมากับ logic

  เริ่มโดย

  1. ทำการเพิ่มฟังก์ชั่น getBookByID โดย ฟังก์ชั่นนี้สร้างหลังจากฟังก์ชั่น getBooks
func getBookByID(c *gin.Context) { 
  paramID := c.Param("id")
  for _, book := range books {
   //ทำการ loop หาหนังสือที่ตรงกับ id ที่ส่งมา
    if book.ID == paramID { 
      c.JSON(http.StatusOK, book)
      return
    }
  }
 c.JSON(http.StatusNotFound, "data not found")
}

  ในโค้ดข้างต้นนี้:

  • Context.Param ใช้สำหรับเรียกข้อมูลที่ส่งมาเป็น param โดยใส่ key ที่ต้องการ value ในโค้ดนี้ต้องการค่า id ที่ส่งมา
  • logic สำหรับฟังก์ชั่นนี้จะทำการ loop หา หนังสือตาม id ที่ส่งมาและส่งรายการหนังสือนั่นกลับไป
  • หาก id ที่ส่งมาไม่ตรงกับ id รายการหนังสือจะส่ง status code 404 พร้อม ข้อความ “data not found”

 

  2. สร้าง route GET /book/:idสำหรับเรียกฟังก์ชั่น getBookByID ในฟังก์ชั่น main

func main() {
    router := gin.Default()
    router.GET("/books", getBooks)
    router.GET("/book/:id", getBookByID)
    router.Run("localhost:8080")
}

 

  3.ทำการ save ไฟล์ main.go ทำการ Run ด้วย command go run main.go

  ทำการ requests web service ที่เราเขียนไว้

  1. ใส่ url localhost:8080/books/1และใช้เมธอด GET send request
  2. เมื่อกด send จะได้รายการหนังสือรายการที่ id เท่ากับ 1 ส่งมาเป็น object พร้อม status 200

ผลลัพธ์การเรียก API[GET] /books/:id

 

  เขียนตัวจัดการและเพิ่มข้อมูล

   เมื่อผู้ใช้ request POST /booksที่เพิ่มรายการหนังสือไปใน request body สิ่งที่ผู้ใช้ต้องได้คือจะมีรายการหนังสือจากที่เราเพิ่มเข้าไปจากรายการหนังสือที่มีอยู่

  • ทำ logic สำหรับ เพิ่มรายการหนังสือจากรายการหนังสือที่มีอยู่
  • จับคู่ระหว่าง request ที่เรียกมากับ logic
  1. ทำการเพิ่มฟังก์ชั่น addBook ต่อจาก ฟังก์ชั่น getBookByID
func addBook(c *gin.Context) {
    var newBook book

    // เรียก BindJSON เพื่อผูก JSON ที่รับมากับ newBook
    if err := c.BindJSON(&newBook); err != nil {
        return
    }

    // เพิ่มรายการหนังสือเล่มใหม่เข้าไปใน slice
    books = append(books, newBook)
    c.JSON(http.StatusCreated, newBook)
}

  ในโค้ดข้างต้นนี้:

  • ประกาศ newBook โดย type เป็น book
  • Context.BindJSON ใช้สำหรับผูก request body กับ newBook
  • apped ในโค้ดนี้จะทำการเพิ่ม newBook ต่อท้าย JSON ของ books
  • http.StatusCreated เป็นค่าคงที่มีค่าเป็น 201

 

  2. สร้าง route POST/bookสำหรับเรียกฟังก์ชั่น addBookในฟังก์ชั่น main

func main() {
    router := gin.Default()
    router.GET("/books", getBooks)
    router.GET("/book/:id", getBookByID)
    router.POST("/books", addBook)
    router.Run("localhost:8080")
}

 

  3. ทำการ save ไฟล์ main.go และ Run ด้วย command go run main.go

ทำการ requests web service ที่เราเขียนไว้

  1. ใส่ url localhost:8080/booksและใช้เมธอด POST send request
  2. เลือก Body รูปแบบ raw และเลือกเป็น JSON
  3. ใส่รายการหนังสือที่เราจะเพิ่มเข้าไป
{
      "ID": "4", 
      "Name": "RICH DAD POOR DAD", 
      "Author": "Robert Kiyosaki", 
      "Price": 14.9
 }

   

  4. เมื่อกด send จะได้รายการหนังสือที่เพิ่มมาเป็น object พร้อม status 201

ผลลัพธ์การเรียก API [POST] /books

 

  เขียนตัวจัดการและแก้ไขข้อมูล

   เมื่อผู้ใช้ request PUT /book/:id โดยส่ง parameter id ไปสำหรับแก้ไขข้อมูลรายการหนังสือตาม id ที่ส่งไป

  • ทำ logic สำหรับแก้ไขข้อมูลรายการหนังสือตาม id ที่ส่งไปจากรายการหนังสือที่มีอยู่
  • จับคู่ระหว่าง request ที่เรียกมากับ logic
  1. ทำการเพิ่มฟังก์ชั่น updateBook ต่อจาก ฟังก์ชั่น addBook
func updateBook(c *gin.Context) {
  var editBook book  if err := c.BindJSON(&editBook); err != nil {
    return
  }
  
  paramID := c.Param("id")
  for i := 0; i <= len(books)-1; i++ {
    //loop หา id หนังสือตาม id และทำการแก้ไขรายการหนังสือนั่น
    if books[i].ID == paramID {
      books[i].Name = editBook.Name
      books[i].Author = editBook.Author
      books[i].Price = editBook.Price
      
      c.JSON(http.StatusOK, books[i])
      return
    }
 }
 
 c.JSON(http.StatusNotFound, "data not found")
}

  ในโค้ดข้างต้นนี้:

  • ประกาศ editBook โดย type เป็น book
  • Context.BindJSON ใช้สำหรับผูก request body กับ editBook
  • ทำ logic ในการแก้ไขข้อมูลหนังสือตาม parameter id ที่ส่งมาให้
  • หาก id ที่ส่งมาไม่ตรงกับ id รายการหนังสือจะส่ง status code 404 พร้อม ข้อความ “data not found”

 

  2. ทำการเพิ่ม route สำหรับเรียกฟังก์ชั่นที่มาจัดการแก้ไขหนังสือ

func main() {
    router := gin.Default()
    router.GET("/books", getBooks)
    router.GET("/book/:id", getBookByID)
    router.POST("/books", addBook)
    router.PUT("/book/:id", updateBook)    router.Run("localhost:8080")
}

  3. ทำการ save ไฟล์ main.go และ Run ด้วย command go run main.go

 

ทำการ requests web service ที่เราเขียนไว้

  1. ใส่ url localhost:8080/book/1และใช้เมธอด PUT send request
  2. เลือก Body รูปแบบ raw และเลือกเป็น JSON
  3. ใส่ข้อมูลที่เราจะแก้ไขรายการหนังสือ
{
      "Name": "RICH DAD POOR DAD", 
      "Author": "Robert Kiyosaki", 
      "Price": 14.9
 }

 

   4. เมื่อกด send จะได้รายการหนังสือที่แก้ไขของหนังสือ id เท่ากับ 1 มาเป็น object พร้อม status 200

ผลลัพธ์การเรียก API [PUT] /book/1

 

  5.เช็ครายการหนังสือทั้งหมดโดย request รายการหนังสือทั้งหมดเพื่อตรวจสอบความถูกต้อง ใช้ API Route GET /books จะเห็นว่า รายการหนังสือแรกมีข้อมูลที่เราทำการแก้ไขเรียบร้อยแล้ว

ผลลัพธ์การเรียก API [GET] /books

 

  เขียนตัวจัดการและลบข้อมูล

   เมื่อผู้ใช้ request DELETE /book/:id โดยส่ง parameter id ไปสำหรับลบข้อมูลรายการหนังสือตาม id ที่ส่งไป

  • ทำ logic สำหรับลบรายการหนังสือตาม id ที่ส่งไปจากรายการหนังสือที่มีอยู่
  • จับคู่ระหว่าง request ที่เรียกมากับ logic
  1. ทำการเพิ่มฟังก์ชั่น deleteBookByIDต่อจาก ฟังก์ชั่น updateBook
func deleteBookByID(c *gin.Context) {
   paramID := c.Param("id")
   for i := 0; i <= len(books)-1; i++ {
     if books[i].ID == paramID {
// ทำการลบรายการหนังสือจาก id ที่ส่งมา 
//โดยตัดข้อมูลแล้วเอามาต่อโดยไม่มีรายการหนังสือที่เราส่งมา      
   books = append(books[:i], books[i+1:]...)
       c.JSON(http.StatusOK, "delete success")
       
       return
     }
   }
   c.JSON(http.StatusNotFound, "data not found")
}       books = append(books[:i], books[i+1:]...)
       c.JSON(http.StatusOK, "delete success")
       
       return
     }
   }
   c.JSON(http.StatusNotFound, "data not found")
}

  ในโค้ดข้างต้นนี้:

  • logic สำหรับฟังก์ชั่นนี้จะทำการ loop หา หนังสือตาม id ที่ส่งมา ทำการลบรายการหนังสือเล่มนั้นโดยใช้วิธีการรวมรายการหนังสือใหม่ และส่งข้อความกลับมาว่า “delete success”
  • หาก id ที่ส่งมาไม่ตรงกับ id รายการหนังสือจะส่ง status code 404 พร้อม ข้อความ “data not found”

 

  2. ทำการเพิ่ม route สำหรับเรียกฟังก์ชั่นที่มาจัดการลบรายการหนังสือ

func main() {
    router := gin.Default()
    router.GET("/books", getBooks)
    router.GET("/book/:id", getBookByID)
    router.POST("/books", addBook)
    router.PUT("/book/:id", updateBook)
    router.DELETE("/book/:id", deleteBookByID)    router.Run("localhost:8080")
}

 

  3. ทำการ save ไฟล์ main.go และ Run ด้วย command go run . หรือ go run main.go

ทำการ requests web service ที่เราเขียนไว้

  1. ใส่ url localhost:8080/book/2 และใช้เมธอด DELETE send request
  2. เมื่อกด send จะได้รับข้อความ “delete success” พร้อม status 200

ผลลัพธ์การเรียก API [DELETE] /book/2

 

  3.เช็ครายการหนังสือทั้งหมดโดย request รายการหนังสือทั้งหมดเพื่อตรวจสอบความถูกต้อง ใช้ API Route DELETE/books/:id จะเห็นว่า รายการหนังสือได้ถูกลบไปแล้ว

ผลลัพธ์การเรียก API [GET] /books

 

  ไฟล์ main.go จากหัวข้อทั้งหมด

 

   เสร็จแล้วกับการเขียน RESTful ด้วยภาษา Go และใช้ gin framwork บทความนี้เกิดจากความหลงไหลของภาษา Go และอยากเริ่มต้นจากสิ่งที่จับต้องได้โดยทำ RESTful ในรูปแบบ CRUD หวังว่าบทความนี้จะเป็นประโยชน์ของผู้เริ่มต้นภาษา Go นะครับ

 

References

 

tags : golang gin-gonic restful api



ติดตามข่าวสารและเรื่องราวดีๆ ทาง Email