ตอนที่ 2 สร้าง RESTful API ด้วยภาษา Go (Fiber) 🔥

suphakit
6 min readSep 6, 2023

--

จากบทความที่แล้วเราได้มีการสร้างโปรเจกต์ง่ายๆ ด้วย Fiber (เว็บเฟรมเวิร์กบนภาษา Go) ในบทความนี้เราจะมาเจาะลึกขึ้นในการใช้ Method ต่างๆ ว่าแต่ละตัวต่างกันยังไง มีหน้าที่แบบไหน พร้อมแล้วก็ไปลุยกันเลย 😊

Setting Project ⚙️

STEP 0: ไฟล์ตั้งต้น

แนะนำให้เริ่มอ่านจาก ตอนที่1 มาก่อนนะครับ แต่หากใครที่พึ่งเข้ามาเริ่มอ่านในบทความนี้ ส่วนนี้คือไฟล์ตั้งต้นครับผม

package main

import (
"fmt"
"github.com/gofiber/fiber/v2"
)

func main() {
fmt.Println("hello world")

// fiber instance
app := fiber.New()

// routes
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("hello world 🌈")
})

// app listening at PORT: 3000
app.Listen(":3000")
}

STEP 1: ออกแบบ RESTful API

ก่อนจะลงมือสร้าง เราก็ต้องมาออกแบบ API กันก่อน โดยเราจะมาสร้าง API ของอาหารกันดังนี้เลย

GET "/foods"        ค้นหาข้อมูลอาหารทั้งหมด
GET "/foods/:id" ค้นหาข้อมูลอาหารตามไอดี
POST "/foods" สร้างข้อมูลอาหาร
PUT "/foods/:id" แก้ไขข้อมูลอาหารตามไอดี
DELTE "/foods/:id" ลบข้อมูลอาหารตามไอดี

STEP 2: สร้าง Struct และจำลอง Data

ในตอนนี้เราจะยังไม่มีการเชื่อมต่อถึง Database ดังนั้นเราจะใช้โครงสร้างข้อมูลภายในไฟล์ main.go แบบง่ายๆ ดังนี้

type Food struct {
ID uint `json:"id"`
Name string `json:"name"`
Price uint `json:"price"`
}

var foods = []Food{
{ID: 1, Name: "ต้มยำกุ้ง", Price: 140},
{ID: 2, Name: "ไก่ทอด", Price: 100},
{ID: 3, Name: "ก๋วยเตี๋ยว", Price: 30},
{ID: 4, Name: "เบอร์เกอร์", Price: 149},
}

stuct คือโครงสร้างข้อมูลที่เรากำหนดได้เอง โดยจะประกอบไปด้วย fields ต่างๆ ในที่นี้จะเป็นโครงสร้างข้อมูลของ Food สังเกตได้ว่าจะมี Tag `json:”id”` ต่อท้ายอยู่ ซึ่ง tag เหล่านี้มีหน้าที่บอกให้ Go สามารถรู้ว่าควรจะเชื่อมเอา Field อันไหนใน JSON มาเชื่อมกับ Field ไหนใน Struct ของเรานั่นเอง

STEP 3: ติดตั้ง Postman

เนื่องจากมีการใช้หลาย Method ในบทความนี้ (Get, Post, Put, Delete, …) หากทดสอบตามบทความที่แล้วที่เราเข้าผ่าน URL จะสามารถทดสอบได้แค่เฉพาะ Method Get เราจึงใช้ Postman ในการทดสอบ API ของเรา หากใครยังไม่ได้ติดตั้งสามารถดาวน์โหลดได้ที่นี้เลย 👉 download

มาเริ่มสร้าง RESTful API กันเลย 🌈

STEP 1: สร้าง API สำหรับค้นหาข้อมูลทั้งหมด

Method       GET
Path /foods
Description ค้นหาข้อมูลอาหารทั้งหมด
Return JSON

จากที่เราออกแบบไว้ข้างต้น เราสามารถ implement ได้โค้ดดังนี้

 app.Get("/foods", func(c *fiber.Ctx) error {
return c.JSON(foods)
})

ทดสอบ 🧪

เราจะใช้ Postman โดยใส่ URL ของเราเป็น http://localhost:3000/foods และกด Send จะได้ผลลัพธ์หน้าตาแบบนี้ ซึ่งก็คือข้อมูลของ foods นั้นเอง

STEP 2: สร้าง API สำหรับค้นหาข้อมูลตามไอดี

Method       POST
Path /foods
Description สร้างข้อมูลอาหาร
Return JSON

เราจะมีการส่งเลข id ผ่านลงบน path เช่น /foods/1 ซึ่งหมายถึงว่าค้นหาข้อมูลอาหารไอดีที่ 1

 app.Get("/foods/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
for _, food := range foods {
if food.ID == id {
return c.JSON(food)
}
}
return nil
})

อธิบาย 💭

  1. เราสามารถใช้ Feature ของ Fiber ที่เป็น Path Parameters โดยแทรก :id เข้าไปใน path และเรียกใช้ผ่านคำสั่ง id := c.Params(“id”) เพื่อดึง Param เข้ามาใช้เป็นตัวแปรของเรา
  2. หลังจากทำการวนลูปภายใน foods เพื่อหา food ที่มีเลข ID ตรงกับเลข id ที่เราดึงมาจาก Params เมื่อเจอก็จะส่งข้อมูล food นั้นกลับเป็น JSON

ทดสอบ 🧪

ใช้ Postman โดยใส่ URL ของเราเป็น http://localhost:3000/foods/1 และกด Send จะได้ผลลัพธ์หน้าตาแบบนี้ ซึ่งก็คือข้อมูลของ food นั้นเอง อย่าลืมเทสกับเลข id ตัวอื่นๆด้วยนะครับ

STEP 3: สร้าง API สำหรับสร้างข้อมูลอาหาร

Method       POST
Path /foods
Description สร้างข้อมูลอาหาร
Return JSON

เราจะมาสร้างอาหารกันบ้างดีกว่า ตามโค้ดด้านล่างนี้เลย

 app.Post("/foods", func(c *fiber.Ctx) error {
var food Food
if err := c.BodyParser(&food); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
foods = append(foods, food)
return c.JSON(food)
})

อธิบาย 💭

  1. เนื่องจากเราจะทำการสร้างข้อมูลอาหาร พวกรายละเอียดของอาหารต่างๆนั้น จะถูกส่งมาผ่าน Body ของ Request เราจึงใช้คำสั่ง c.BodyParser(&food) เพื่อทำการ Bind ข้อมูลจาก Body มาเก็บไว้ในตัวแปร Struct ที่มีชื่อว่า food
  2. เมื่อรับข้อมูลมาแล้วไม่เกิด Error ใดๆ เราจะใช้คำสั่ง foods = append(foods, food) เพื่อเพิ่มข้อมูลลงตัวแปร foods และคืนค่า food ที่เราพึ่งสร้างส่งคืนกลับ Client นั่นเอง

ทดสอบ 🧪

มาลองทดสอบกับ Postman กันต่อเลย แต่จะมีสิ่งที่เปลี่ยนไปเล็กน้อย

  1. เข้า POSTMAN
  2. ใช้ URL http://localhost:3000/foods
  3. เปลี่ยน Method เป็น POST
  4. กดไปที่ Body -> raw -> JSON เพื่อจะทำการส่งข้อมูลประเภท JSON ผ่าน Body
  5. ใส่ข้อมูลอาหารที่เราต้องการจะสร้าง ในตอนนี้ผมรู้สึกอยากกินกะเพราะหมูกรอบดังนั้น Body ของผมจะหน้าตาแบบนี้
{
"id": "5",
"name": "กะเพราหมูกรอบ",
"price": 50
}

และเมื่อทดสอบผ่าน Postman จะได้ผลลัพธ์แบบนี้

STEP 4: สร้าง API สำหรับแก้ไขข้อมูลอาหารตามไอดี

Method       PUT
Path /foods/:id
Description แก้ไขข้อมูลอาหารตามไอดี
Return JSON

เราจำเป็นต้องสร้าง struct ขึ้นมาใหม่โดยมีชนิดข้อมูลเป็นดังนี้

type EditFood struct {
Name string `json:"name"`
Price uint `json:"price"`
}

จากนั้นไปลุยต่อกันที่โค้ดด้านล่างนี้เลย

 app.Put("/foods/:id", func(c *fiber.Ctx) error {
var editFood EditFood
if err := c.BodyParser(&editFood); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}

id := c.Params("id")
for i := range foods {
if foods[i].ID == id {
foods[i].Name = editFood.Name
foods[i].Price = editFood.Price
return c.JSON(foods[i])
}
}
return nil
})

อธิบาย 💭

  1. สร้างข้อมูล Struct editFood ขึ้นมาเพื่อรับเจ้า Body จาก Request
  2. สร้างตัวแปร id ที่ดึงข้อมูลมาจาก Param ของเรา ซึ่งเจ้า id นี่แหละที่เราจะทำการแก้ไขข้อมูลกัน
  3. ทำการวนลูปภายใน foods เพื่อหา food id นั้นจากนั้นทำการแก้ค่า และ return ค่ากลับเป็น JSON

ทดสอบ 🧪

  1. เข้า POSTMAN
  2. ใช้ URL http://localhost:3000/foods/1
  3. เปลี่ยน Method เป็น PUT
  4. ใส่ Name และ Price ที่ต้องการจะเปลี่ยนดังนี้
{
"name": "ราเมน",
"price": 120
}

และเมื่อทดสอบจะได้ผลลัพธ์ดังนี้

STEP 5: สร้าง API สำหรับลบข้อมูลอาหารตามไอดี

Method       DELETE
Path /foods/:id
Description ลบข้อมูลอาหารตามไอดี
Return String

มาถึง Method ตัวสุดท้ายกันแล้วครับ นั่นก็คือ Delete นั่นเอง โดยโค้ดจะเป็นดังนี้

 app.Delete("/foods/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
for i, food := range foods {
if food.ID == id {
foods = append(foods[:i], foods[i+1:]...)
return c.sen("delete success")
}
}
return nil
})

อธิบาย 💭

  1. สร้างตัวแปร id ที่ดึงข้อมูลมาจาก Param ของเรา เพื่อนำไปค้นหาใน foods
  2. ทำการวนลูปเพื่อหา food ตัวที่เราต้องการจะลบ
  3. เมื่อเจอจะใช้คำสั่ง foods = append(foods[:i], foods[i+1:]...) ซึ่งเป็นการเลือกเฉพาะ food ที่อยู่ก่อนหน้าและอยู่หลัง food ตัวที่ต้องการจะลบ เก็บเข้าสู่ตัวแปร foods ของเรา
  4. return ค่ากลับเป็นคำว่า “delete success”

ทดสอบ 🧪

  1. เข้า POST
  2. ใช้ URL http://localhost:3000/foods/1
  3. เปลี่ยน Method เป็น DELETE

Refactor Code กันดีกว่า

นี่คือโค้ดทั้งหมดของเรา หน้าตาจะเป็นแบบนี้

package main

import (
"fmt"

"github.com/gofiber/fiber/v2"
)

type Food struct {
ID string `json:"id"`
Name string `json:"name"`
Price uint `json:"price"`
}

type EditFood struct {
Name string `json:"name"`
Price uint `json:"price"`
}

var foods = []Food{
{ID: "1", Name: "ต้มยำกุ้ง", Price: 140},
{ID: "2", Name: "ไก่ทอด", Price: 100},
{ID: "3", Name: "ก๋วยเตี๋ยว", Price: 30},
{ID: "4", Name: "เบอร์เกอร์", Price: 149},
}

func main() {
fmt.Println("hello world")

// fiber instance
app := fiber.New()

// routes
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("hello world 🌈")
})

app.Get("/foods", func(c *fiber.Ctx) error {
return c.JSON(foods)
})

app.Get("/foods/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
for _, food := range foods {
if food.ID == id {
return c.JSON(food)
}
}
return nil
})

app.Post("/foods", func(c *fiber.Ctx) error {
var food Food
if err := c.BodyParser(&food); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
foods = append(foods, food)
return c.JSON(food)
})

app.Put("/foods/:id", func(c *fiber.Ctx) error {
var editFood EditFood
if err := c.BodyParser(&editFood); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}

id := c.Params("id")
for i := range foods {
if foods[i].ID == id {
foods[i].Name = editFood.Name
foods[i].Price = editFood.Price
return c.JSON(foods[i])
}
}
return nil
})

app.Delete("/foods/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
for i, food := range foods {
if food.ID == id {
foods = append(foods[:i], foods[i+1:]...)
return c.SendString("delete success")
}
}
return nil
})

// app listening at PORT: 3000
app.Listen(":3000")
}

สังเกตได้ว่าโค้ดเราดูอ่านค่อนข้างยาก เพื่อความง่ายในการอ่านและการจัดการ เราจะทำ Handler แยกออกมาเป็น Function ของตัวมันเอง

func main() {
fmt.Println("hello world")

// fiber instance
app := fiber.New()

// routes
app.Get("/", helloWorld)
app.Get("/foods", getFoods)
app.Get("/foods/:id", getFoodByID)
app.Post("/foods", createFood)
app.Put("/foods/:id", updateFoodByID)
app.Delete("/foods/:id", deleteFoodByID)

// app listening at PORT: 3000
app.Listen(":3000")
}
func helloWorld(c *fiber.Ctx) error {
return c.SendString("hello world 🌈")
}

func getFoods(c *fiber.Ctx) error {
return c.JSON(foods)
}

func getFoodByID(c *fiber.Ctx) error {
id := c.Params("id")
for _, food := range foods {
if food.ID == id {
return c.JSON(food)
}
}
return nil
}

func createFood(c *fiber.Ctx) error {
var food Food
if err := c.BodyParser(&food); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
foods = append(foods, food)
return c.JSON(food)
}

func updateFoodByID(c *fiber.Ctx) error {
var editFood EditFood
if err := c.BodyParser(&editFood); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
id := c.Params("id")
for i := range foods {
if foods[i].ID == id {
foods[i].Name = editFood.Name
foods[i].Price = editFood.Price
return c.JSON(foods[i])
}
}
return nil
}

และนำพวก Struct มาไว้ด้านล่าง ที่นี้โค้ดของเราก็ดูดีขึ้นแล้วว 😍

type Food struct {
ID string `json:"id"`
Name string `json:"name"`
Price uint `json:"price"`
}

type EditFood struct {
Name string `json:"name"`
Price uint `json:"price"`
}

var foods = []Food{
{ID: "1", Name: "ต้มยำกุ้ง", Price: 140},
{ID: "2", Name: "ไก่ทอด", Price: 100},
{ID: "3", Name: "ก๋วยเตี๋ยว", Price: 30},
{ID: "4", Name: "เบอร์เกอร์", Price: 149},
}

จบกันไปแล้วในตอนที่ 2 เดี๋ยวในตอนต่อไปเราจะมีการเชื่อมต่อกับฐานข้อมูลกันครับขอบคุณที่แวะเข้ามาอ่านนะครับผม กราบๆๆๆ 🙏🏻🙏🏻🙏🏻

--

--