ช่วงนี้ community ของภาษา Go เริ่มมีเยอะขึ้นและมีการใช้อย่างแพร่หลาย เลยเกิดความสนใจในตัวภาษา จึงเริ่มศึกษาก็เกิดความหลงไหลในตัวภาษานี้ เลยตั้งใจลองทำตัว RESTful API ก็ได้ไปเจอ gin framework ที่เป็นที่นิยมของชุมชนของ Go developer ที่จะสามารถช่วยในการทำ RESTful ได้ง่ายและรวดเร็ว
รายการที่เราจะทำในบทความนี้
- Design API endpoints.
- Initiation Project
- สร้างข้อมูลตัวอย่าง
- เขียนตัวจัดการและคืนค่าข้อมูลทุกตัว
- เขียนตัวจัดการและคืนค่าข้อมูลตัวที่เราสนใจ
- เขียนตัวจัดการและเพิ่มข้อมูล
- เขียนตัวจัดการและแก้ไขข้อมูล
- เขียนตัวจัดการและลบข้อมูล
ก่อนจะเริ่มเราต้องมีอะไรบ้าง
- install go แนะนำ version ตั้งแต่ 1.16 หรือมากกว่า install go
- tool to edit สำหรับการเขียนโค้ดแนะนำ VScode
- เครื่องมือในการทดสอบ 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
- เปิด command prompt และเปลี่ยนไปที่ home directory หรือ directory ที่ต้องการจัดเก็บ folder
On Linux or Mac: $ cd
On Windows: C:\> cd %HOMEPATH%
- ใช้ command สร้าง folder
$ mkdir web-service-gin
$ cd web-service-gin
- สร้าง module สำหรับจัดการ dependencies สำหรับกำหนด part ของโค้ดเมื่อ run command นี้เสร็จจะได้ ไฟล์ที่ชื่อ go.mod
$ go mod init example/web-service-gin
- สร้าง file หลักของ project นี้
$ touch main.go
- 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
สร้างข้อมูลตัวอย่าง
- เราจะโค้ดใน file ที่ชื่อว่า
main.go
- ด้านบนสุดของไฟล์จะประกาศชื่อ package
package main
- ใต้ 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
เริ่มโดย
- ทำการเพิ่มฟังก์ชั่น
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
- ใส่ url
localhost:8080/books
ใช้เมธอด GET send request
- เมื่อกด send จะได้รายการหนังสือทั้งหมดมาเป็น Array of object พร้อม status 200
ผลลัพธ์การเรียก API[GET] /books
เขียนตัวจัดการและคืนค่าข้อมูลตัวที่เราสนใจ
เมื่อผู้ใช้ request GET /book/:id
แล้วสิ่งที่ผู้ใช้ต้องได้คือรายการหนังสือตาม id เป็นรูปแบบไฟล์ JSON ดังนั้นเราจะต้องทำ
- ทำ logic สำหรับ return รายการหนังสือตาม paramiter id
- จับคู่ระหว่าง request ที่เรียกมากับ logic
เริ่มโดย
- ทำการเพิ่มฟังก์ชั่น
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 ที่เราเขียนไว้
- ใส่ url
localhost:8080/books/1
และใช้เมธอด GET send request
- เมื่อกด send จะได้รายการหนังสือรายการที่ id เท่ากับ 1 ส่งมาเป็น object พร้อม status 200
ผลลัพธ์การเรียก API[GET] /books/:id
เขียนตัวจัดการและเพิ่มข้อมูล
เมื่อผู้ใช้ request POST /books
ที่เพิ่มรายการหนังสือไปใน request body สิ่งที่ผู้ใช้ต้องได้คือจะมีรายการหนังสือจากที่เราเพิ่มเข้าไปจากรายการหนังสือที่มีอยู่
- ทำ logic สำหรับ เพิ่มรายการหนังสือจากรายการหนังสือที่มีอยู่
- จับคู่ระหว่าง request ที่เรียกมากับ logic
- ทำการเพิ่มฟังก์ชั่น
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 ที่เราเขียนไว้
- ใส่ url
localhost:8080/books
และใช้เมธอด POST send request
- เลือก Body รูปแบบ raw และเลือกเป็น JSON
- ใส่รายการหนังสือที่เราจะเพิ่มเข้าไป
{
"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
- ทำการเพิ่มฟังก์ชั่น
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 ที่เราเขียนไว้
- ใส่ url
localhost:8080/book/1
และใช้เมธอด PUT send request
- เลือก Body รูปแบบ raw และเลือกเป็น JSON
- ใส่ข้อมูลที่เราจะแก้ไขรายการหนังสือ
{
"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
- ทำการเพิ่มฟังก์ชั่น
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 ที่เราเขียนไว้
- ใส่ url
localhost:8080/book/2
และใช้เมธอด DELETE send request
- เมื่อกด 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