บันทึกการเรียน Golang วันที่ 9 - ทำ API ด้วย Chi

Go Sep 27, 2023

สวัสดีครับ ช่วงนี้ดองบทความไว้ ไม่ได้มาเขียนเลย วันนี้เลยขอมาเขียนบันทึกย้อนหลัง ที่เรียน Golang วันที่ 9 นะครับ หลังจากที่วันก่อนได้ลองใช้ gorilla/mux ไป

บันทึกการเรียน Golang วันที่ 8 - ทำ API ง่ายๆ ด้วย gorilla/mux
บันทึกการเรียน Go วันที่ 8 วันนี้เรียนรู้เรื่องการทำ API โดยใช้ตัว gorilla/mux ครับ หลังจากที่อ่านพวกพื้นฐาน ไปบ้างแล้วในเว็บ A Tour of Go (ยังอ่านไม่หมด) ก็เลยคิดว่า ลองทำโปรเจ็คเล็กๆ ขึ้นมา แล้วเรี

วันนี้ผมได้ลองตัว Chi ที่เป็น web framework อีกตัว ก่อนหน้านี้ผมลองดูรายชื่อ framework แล้ว พบว่ามีเยอะมากๆ แต่ขอเลือกตัวที่คล้ายๆ Express เนื่องจากผมเขียน Node.js มาก่อน น่าจะทำให้เข้าใจง่ายกว่า (ลังเลระหว่าง Fiber และ Chi) เลยเลือกตัวนี้ก่อน ค่อยไปลองดู Fiber ในวันถัดไป

Chi

GitHub - go-chi/chi: lightweight, idiomatic and composable router for building Go HTTP services
lightweight, idiomatic and composable router for building Go HTTP services - GitHub - go-chi/chi: lightweight, idiomatic and composable router for building Go HTTP services

เนื่องจากหัดเขียน Go ผมคิดว่าฝึกทำโปรเจ็คและก็เรียนรู้ตัวภาษาไปในตัวและรู้สึกว่าตัวผมเองถนัดแบบนี้ ก็เลยลองคิดทำ API ขึ้นมาง่ายๆ โดยใช้ Chi ดู

ติดตั้ง Chi ง่ายๆ:

go get -u github.com/go-chi/chi/v5

ตัว API ไม่มีอะไรเลยครับ ทำคล้ายๆกับของ วันที่ 8 เปลี่ยนจาก gorilla มาเป็น chi นั่นเอง ตัว Source Code ก็อยู่ท้ายบทความครับ และก็แบ่ง folder เป็นแต่ละวันแล้ว

Routes

กำหนด routes spec ไว้ดังนี้


GET /posts/{id}
POST /posts
GET /posts
GET /users
GET /users/{id}

กำหนด Struct

เนื่องจากว่าผมยังไม่ได้ต่อ database ฉะนั้น ตัว data ก็ทำการ initial แบบ hard code กันไป

package main

type User struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Username string `json:"username"`
	Active   bool   `json:"active"`
}

type Post struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Body     string `json:"body"`
	AuthorID int    `json:"author_id"`
}

type Error struct {
	Message string `json:"message"`
}

var users = []User{
	{ID: "1", Name: "John Doe", Username: "johndoe", Active: true},
	{ID: "2", Name: "Jane Doe", Username: "janedoe", Active: true},
	{ID: "3", Name: "Michael Jordan", Username: "michaeljordan", Active: true},
	{ID: "4", Name: "John Smith", Username: "johnsmith", Active: true},
}

var posts = []Post{
	{ID: "1", Title: "Hello World", Body: "This is my first post", AuthorID: 1},
	{ID: "2", Title: "Hello World 2", Body: "This is my second post", AuthorID: 1},
	{ID: "3", Title: "Hello World 3", Body: "This is my third post", AuthorID: 2},
	{ID: "4", Title: "Hello World 4", Body: "This is my fourth post", AuthorID: 3},
}

Hello World Chi

ถ้าดูจากโค๊ด Hello World จะเห็นว่ามีความแอบคล้ายๆ Express (หรือจริงๆ น่าจะคล้ายๆ Koa ฝั่ง Node.js มากกว่าแฮะ)

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World!"))
    })
    http.ListenAndServe(":3000", r)
}
  • ทำการสร้าง Router ด้วย chi.NewRouter()
  • กำหนด middleware ด้วย Use() คล้ายๆ express
  • handle HTTP method ตามชื่อ function ได้เลย .Get('/path', handler)

ตัวอย่าง กำหนด route และ handler function

r.Get("/posts", FindAllPosts)
r.Post("/posts", CreateNewPost)
r.Get("/posts/{postID}", FindPostByID)
r.Get("/users", FindAllUsers)
r.Get("/users/{userID}", FindUserByID)

การ return JSON เราจะใช้ตัว middleware render ติดตั้ง:

 go get -u github.com/go-chi/render

import มาใช้

import (
	"github.com/go-chi/render"
)

ตัวอย่าง handler function ของ FindAllPosts ใช้ render.JSON ได้เลย รู้สึกว่าสะดวกกว่าการมานั่งปั้น struct json เอง หรือผมอาจจะยังไม่ชินมั้ง

func FindAllUsers(w http.ResponseWriter, r *http.Request) {
	render.JSON(w, r, users)
}

การรับ URL Params

ใช้ chi.URLParam()

r.Get("/posts/{postID}", FindPostByID)
    
func FindPostByID(w http.ResponseWriter, r *http.Request) {
	postID := chi.URLParam(r, "postID")
}

การรับ Body Payload

ใช้ render.DecodeJSON()

func CreateNewPost(w http.ResponseWriter, r *http.Request) {
	var post Post
	err := render.DecodeJSON(r.Body, &post)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("400 - Bad Request"))
		return
	}
}

จากตัวอย่างใน Doc เห็นว่าตัว Chi รองรับการทำ routing groups ได้

func main(){
    r := chi.NewRouter()
    
    // Public Routes
    r.Group(func(r chi.Router) {
        r.Get("/", HelloWorld)
        r.Get("/{AssetUrl}", GetAsset)
        r.Get("/manage/url/{path}", FetchAssetDetailsByURL)
        r.Get("/manage/id/{path}", FetchAssetDetailsByID)
    })

    // Private Routes
    // Require Authentication
    r.Group(func(r chi.Router) {
        r.Use(AuthMiddleware)
        r.Post("/manage", CreateAsset)
    })

}

หลังจากได้ลองเล่นไปเล็กน้อย ก็พบว่า ด้วยความที่มันมีความคล้าย Express เหมือนเราเขียน Node.js ทำให้การฝึกเขียน Go ไม่ได้มีความยากมากเท่าไหร่ และดูเข้าใจง่ายขึ้นเยอะ แน่นอน ผมยังไม่ได้เรียนรู้ตัวภาษา Go อีกเยอะมากมาย แต่ผมเน้นทำ โปรเจ็คเล็กๆ แล้วเรียนรู้ไปเรื่อยๆ ดีกว่า มันเห็นภาพมากกว่า อาจมีผิดบ้าง แต่ก็ได้ผลลัพธ์เป็นที่พอใจ

เดี๋ยวไว้วันต่อไป จะลองเอา API เดิม เริ่มไปต่อ Database ดูบ้างแล้วดีกว่า

Happy Coding ❤️

Source Code

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com