[Golang] How to create a simple API
กลับมาอีกครั้งหลังจากหายไป 1 อาทิตย์ เพราะ หนีไปเที่ยว(ฮ่าๆๆ คนเรามันต้องพัก) และในครั้งนี้เราจะมาพูดกันในเรื่อง การวางโครงสร้างของ api project และ การเขียน handle errors ที่จะเกิดขึ้น
Project structure
จากที่เคยพูดไว้ใน หัวข้อ “Go lang setup and environment” by Visarut Junsone ใน Step 2 ข้อ 2 src เป็นที่ว่าไฟล์ source code ที่ตรงนี้แหละครับ ที่เราจะวาง project folder ของเรา
ที่นี้ในส่วนของโครงสร้างภายในหลาย ๆ คนที่เป็น Developer ที่เคยสร้าง api ด้วยภาษาอื่น ๆ ก็คงจะมีโครงสร้างในใจแล้ว แต่บล็อกนี้ผมจะพาสร้าง โครงสร้างที่ตัวผมคิดว่าเข้าใจง่ายที่สุดกัน สามารถดาวน์โหลดไฟล์มาดูได้ที่ github เลยครับ
เริ่มจากภายใน doge-apis จะเห็นว่ามี apis และ router สองโฟลเดอร์ และ main.go ที่เป็นไฟล์ go ที่เอาไว้เป็นตัวหลักในการเรียกใช้งาน ส่วนใน apis นั้นจะเอาไว้เก็บไฟล์ go ที่ทำการคำนวนหรือทำงานของ api นั้น ๆ และ สุดท้ายใน router จะเก็บเกี่ยวกับการ handle route path และ controller
Main.go
จากใน HTTP router ที่ได้นำเสนอไปในครั้งที่ผ่านมา เราจะเอามาปรับให้ดูมีความเป็น API มืออาชีพสักหน่อย (ขี้โม้สุดๆ)
ในไฟล์ที่กล่าวนี้ จะประกอบไปด้วย 3 function ซึ่งคือ main, notfoundHandler และ errorHandler
func main() { r := routing.New() r.Use(errorHandler) r.NotFound(notFoundHandler) path.RoutePath(r) h := r.HandleRequest h = fasthttp.CompressHandler(h) panic(fasthttp.ListenAndServe(":8080", h))}
ใน main นี้จะสังเกตได้ว่ามีการเพิ่มและปรับเปลี่ยน จาก HTTP router สิ่งที่ปรับเปลี่ยนคือ router.Get ที่เคยมีนั้นถูกย้ายไปไว้ใน RoutePath ซึ่งถูกจัดไว้อยู่ใน Directory “router/routes/routes.go” ซึ่งจะนำมากล่าวเพิ่มหลังจากนี้
สิ่งที่เพิ่ม คือ การใช้ Use(handlers …Handler) เพื่อนำ handler ที่เราเขียนขึ้นมาเองมาผนวกรวมไปกับ routes ที่มีอยู่ ในที่นี้เราใช้ errorHandler เพื่อ รับมือกับกรณี error ต่าง ๆ
func errorHandler(c *routing.Context) error { c.SetContentType("application/json") defer func(c *routing.Context) { if rec := recover(); rec != nil { fmt.Println("Recovered in f", rec) switch x := rec.(type) { case string: response := `{ "message": "` + fmt.Sprintf("%s", x) + `" }` c.SetStatusCode(400) c.RequestCtx.SetBodyString(response) case error: response := `{ "message": "` + fmt.Sprintf("%s", x.(error)) + `" }` c.SetStatusCode(400) c.RequestCtx.SetBodyString(response) default: response := `{ "message": "Something wrong. Fix it baka!" }` c.SetStatusCode(400) c.RequestCtx.SetBodyString(response) } c.Abort() } }(c) c.Next()
return nil}
NotFound(handlers …Handler) เพื่อระบุ handler ที่ควรจะถูกเรียกเมื่อ router ไม่สามารถหา routes ที่เหมาะสมได้ ในที่นี้เราใช้ notFoundHandler
func notFoundHandler(c *routing.Context) error { c.RequestCtx.NotFound() c.Next() return nil}
CompressHandler(h RequestHandler) เพื่อทำการบีบอัด request ที่สร้างขึ้นหาก header มีการกำหนด Accept-Encoding
Routes
ภายในโฟล์เดอร์จะเอาไว้เก็บรวบรวมไฟล์สำหรับการ route path ต่าง ๆ ที่จะชี้ไปหา controller ที่ต้องการ ตัวอย่างเช่นในไฟล์ routes.go
func RoutePath(r *routing.Router) { v := r.Group("/doge") v.Get("", controller.HelloThereCtrl)}
ในตัวอย่างจะมีการ group ก่อน เพราะฉะนั้นเวลาที่เราเรียก ลิ้งค์จะเป็นในลักษณะนี้ localhost:8080/doge และเรียกด้วย Method GET ก็จะถูกพาเข้าสู่ controller ชื่อ HelloThereCtrl
Controller
ภายในโฟล์เดอร์จะเอาไว้เก็บรวบรวมไฟล์สำหรับ controller ต่าง ๆ ที่จะชี้ไปการ api ของตัวที่ต้องการ ใน controller นี้จะมีหน้าที่ในการควบคุมการ response ออกไปหา client ตัวอย่างเช่นในไฟล์ helloCtrl.go
func HelloThereCtrl(c *routing.Context) error { c.SetStatusCode(200) response := `{ "message": "` + fmt.Sprintf("%s", apis.HelloThere()) + `" }` c.SetBodyString(response) return nil}
จะเห็นว่ามีการเรียก apis.HelloThere() เพื่อเรียกการคำนวนจาก apis โฟล์เดอร์
Apis
ภายในโฟล์เดอร์นี้จะเก็บรวบรวมไฟล์ที่เกียวกับการคำนวนของ apis แต่ละตัวของมันเอง เช่น การ สร้างคำว่า “Hello, world!” แบบในตัวอย่าง ที่ hello.go
func HelloThere() string { return "Hello, world!"}
เมื่อเราบอกเป็น step จะเห็นได้ว่า เส้นทางจะเป็นในลักษณะนี้
- ทำการเรียก localhost:8080/doge ด้วย Method GET
- ผ่านเข้าไปยัง RoutePath ที่สร้างไว้วิ่งไปหา HelloThereCtrl
- ทำการเรียกการส่งคำ “Hello, world!” จาก HelloThere()
เรามาทดสอบกันเถอะครับ ด้วยการ “go run main.go” !!!!!
จบกันแล้วครับ กับตัวอย่างง่าย ๆ สไตล์คน ขี้เกียจ เรื่องต่อไปจะเป็นอะไร จะยังเกี่ยวกับ golang หรือไหม ต้องคอยดู (ยังคิดไม่ออก ฮ่าๆๆ) ขอบคุณอ่านจนจบครับ