[Golang] How to create a simple API

Visarut Junsone
Lazy-Dev
Published in
2 min readJan 20, 2019

กลับมาอีกครั้งหลังจากหายไป 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 เลยครับ

รูปที่ 1 ตัวอย่างโครงสร้างไฟล์

เริ่มจากภายใน 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 จะเห็นได้ว่า เส้นทางจะเป็นในลักษณะนี้

  1. ทำการเรียก localhost:8080/doge ด้วย Method GET
  2. ผ่านเข้าไปยัง RoutePath ที่สร้างไว้วิ่งไปหา HelloThereCtrl
  3. ทำการเรียกการส่งคำ “Hello, world!” จาก HelloThere()

เรามาทดสอบกันเถอะครับ ด้วยการ “go run main.go” !!!!!

รูปที่ 2 แสดงตัวอย่างการเรียก api

จบกันแล้วครับ กับตัวอย่างง่าย ๆ สไตล์คน ขี้เกียจ เรื่องต่อไปจะเป็นอะไร จะยังเกี่ยวกับ golang หรือไหม ต้องคอยดู (ยังคิดไม่ออก ฮ่าๆๆ) ขอบคุณอ่านจนจบครับ

--

--

Visarut Junsone
Lazy-Dev

I’m Full-Stack Developer. ReactJS Golang C# Javascript C++ PHP and learning a lot more. Not lazy as it name. “Lazy-Dev”