Skip to main content

แนะนำ CRUD / Request

เริ่มต้น CRUD

Ref: https://docs.gofiber.io/guide/routing

มาเริ่มต้นด้วยการเพิ่ม HTTP Method ของ API กัน โดยปกติ Fiber ได้เตรียม method ไว้ให้ใช้ตามนี้

// HTTP methods
func (app *App) Get(path string, handlers ...Handler) Router
func (app *App) Head(path string, handlers ...Handler) Router
func (app *App) Post(path string, handlers ...Handler) Router
func (app *App) Put(path string, handlers ...Handler) Router
func (app *App) Delete(path string, handlers ...Handler) Router
func (app *App) Connect(path string, handlers ...Handler) Router
func (app *App) Options(path string, handlers ...Handler) Router
func (app *App) Trace(path string, handlers ...Handler) Router
func (app *App) Patch(path string, handlers ...Handler) Router

// Add allows you to specifiy a method as value
func (app *App) Add(method, path string, handlers ...Handler) Router

// All will register the route on all HTTP methods
// Almost the same as app.Use but not bound to prefixes
func (app *App) All(path string, handlers ...Handler) Router

ในเริ่มต้นนี้เราจะ

  • ทำ CRUD ซึ่งประกอบด้วย method ทั้งหมด 4 อันคือ GET, POST, PUT, DELETE
  • ทำการเพิ่มข้อมูลหนังสือเข้า struct เก็บไว้ก่อน
  • และทำการเรียกใช้ผ่าน struct ออกมาได้

** เนื่องจากเรายังไม่มี database = ตอนเราปิด process ไป ข้อมูลทั้งหมดจะหายไป

ซึ่ง function จะมีด้วยกันคือ

  • getBooks = ดึงหนังสือทั้งหมด
  • getBook = ดึงหนังสือตาม id
  • createBook = สร้างหนังสือใหม่
  • updateBook = แก้ไขข้อมูลหนังสือเดิมผ่าน id
  • deleteBook = ลบหนังสือ

ซึ่งเราจะนำทั้งหมดไปประกอบกับ REST API ออกมา

package main

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

// Book struct to hold book data
type Book struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}

var books []Book // Slice to store books

func main() {
app := fiber.New()

// Initialize in-memory data
books = append(books, Book{ID: 1, Title: "1984", Author: "George Orwell"})
books = append(books, Book{ID: 2, Title: "The Great Gatsby", Author: "F. Scott Fitzgerald"})

// CRUD routes
app.Get("/book", getBooks)
app.Get("/book/:id", getBook)
app.Post("/book", createBook)
app.Put("/book/:id", updateBook)
app.Delete("/book/:id", deleteBook)

app.Listen(":8080")
}

// Handlers
func getBooks(c *fiber.Ctx) error {
return c.JSON(books)
}

func getBook(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}

for _, book := range books {
if book.ID == id {
return c.JSON(book)
}
}

return c.SendStatus(fiber.StatusNotFound)
}

func createBook(c *fiber.Ctx) error {
book := new(Book)

if err := c.BodyParser(book); err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}

book.ID = len(books) + 1
books = append(books, *book)

return c.JSON(book)
}

func updateBook(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}

bookUpdate := new(Book)
if err := c.BodyParser(bookUpdate); err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}

for i, book := range books {
if book.ID == id {
book.Title = bookUpdate.Title
book.Author = bookUpdate.Author
books[i] = book
return c.JSON(book)
}
}

return c.SendStatus(fiber.StatusNotFound)
}

func deleteBook(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}

for i, book := range books {
if book.ID == id {
books = append(books[:i], books[i+1:]...)
return c.SendStatus(fiber.StatusNoContent)
}
}

return c.SendStatus(fiber.StatusNotFound)
}

ให้ข้อสังเกต 1 อย่าง

  • ในการ return ของ function fiber Handler จะทำการ return เป็น error ออกไป
  • Fiber expected ผ่านการ return ว่า ค่า error ที่ออกมาต้องออกมาเป็น nil โดย
    • ถ้าเป็น success case = return nil ออกมา และ Fiber ก็จะทำการนำค่าที่ return ไปใช้ตามปกติ
    • ถ้าเป็น fail = return error ออกไป และ Fiber จะสามารถตรวจจับ error และ handle error ออกไปเป็นแบบเคสเดียวกับ go ได้

แยก file Router

Note

  • เราสามารถแยกไฟล์ออกจาก main.go ได้โดยสามารถแยกตาม package
  • หรือจะแยกให้อยู่ใน main เหมือนกัน แต่คนละ file ก็ได้ (อยู่ directory เดียวกัน แต่ประกาศเป็น package main เหมือนกัน ก็จะสามารถเรียกหากันได้)

แยกเป็น

  • main.go
package main

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

func main() {
app := fiber.New()

// Setup routes
app.Get("/book", getBooks)
app.Get("/book/:id", getBook)
app.Post("/book", createBook)
app.Put("/book/:id", updateBook)
app.Delete("/book/:id", deleteBook)

app.Listen(":8080")
}
  • book.go
package main

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

// Book struct to hold book data
type Book struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}

// Initialize in-memory data
var books []Book = []Book{
{ID: 1, Title: "1984", Author: "George Orwell"},
{ID: 2, Title: "The Great Gatsby", Author: "F. Scott Fitzgerald"},
}

// Handler functions

func getBooks(c *fiber.Ctx) error {
return c.JSON(books)
}

func getBook(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}

for _, book := range books {
if book.ID == id {
return c.JSON(book)
}
}

return c.SendStatus(fiber.StatusNotFound)
}

func createBook(c *fiber.Ctx) error {
book := new(Book)

if err := c.BodyParser(book); err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}

book.ID = len(books) + 1
books = append(books, *book)

return c.JSON(book)
}

func updateBook(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}

updatedBook := new(Book)
if err := c.BodyParser(updatedBook); err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}

for i, book := range books {
if book.ID == id {
book.Title = updatedBook.Title
book.Author = updatedBook.Author
books[i] = book
return c.JSON(book)
}
}

return c.SendStatus(fiber.StatusNotFound)
}

func deleteBook(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}

for i, book := range books {
if book.ID == id {
books = append(books[:i], books[i+1:]...)
return c.SendStatus(fiber.StatusNoContent)
}
}

return c.SendStatus(fiber.StatusNotFound)
}

เวลา run command ให้เปลี่ยนจากการ run ราย file เป็น run ทุกไฟล์แทน

# run แบบปกติ
go run *.go
go run .

# run แบบ build
go build

เปิด CORS

  • กรณีอยากทดสอบต่อกับ Frontend = สามารถเปิด CORS (เพิ่ม config เข้าไปได้)
  • library fiber มี support เรื่อง CORS อยู่แล้ว
package main

import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
)

func main() {
app := fiber.New()

// Apply CORS middleware
app.Use(cors.New(cors.Config{
AllowOrigins: "*", // Adjust this to be more restrictive if needed
AllowMethods: "GET,POST,HEAD,PUT,DELETE,PATCH",
AllowHeaders: "Origin, Content-Type, Accept",
}))

// Setup routes
app.Get("/book", getBooks)
app.Get("/book/:id", getBook)
app.Post("/book", createBook)
app.Put("/book/:id", updateBook)
app.Delete("/book/:id", deleteBook)

app.Listen(":8080")
}

upload file

Note

  • ลองมารับ file ด้วยรูปแบบอื่นนอกจาก JSON กันบ้าง เปลี่ยนมารับไฟล์จาก FormData แทน
  • เช่นเคสนี้ เราจะลอง upload ภาพเข้ามา > แล้วนำภาพมา upload ลง directory เอาไว้
package main

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

func uploadImage(c *fiber.Ctx) error {
// Read file from request
file, err := c.FormFile("image")
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}

// Save the file to the server
err = c.SaveFile(file, "./uploads/" + file.Filename)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}

return c.SendString("File uploaded successfully: " + file.Filename)
}

func main() {
app := fiber.New()

// Setup route
app.Post("/upload", uploadImage)

app.Listen(":8080")
}