บันทึกการเรียน Golang วันที่ 8 - ทำ API ง่ายๆ ด้วย gorilla/mux
บันทึกการเรียน Go วันที่ 8 วันนี้เรียนรู้เรื่องการทำ API โดยใช้ตัว gorilla/mux ครับ หลังจากที่อ่านพวกพื้นฐาน ไปบ้างแล้วในเว็บ A Tour of Go (ยังอ่านไม่หมด) ก็เลยคิดว่า ลองทำโปรเจ็คเล็กๆ ขึ้นมา แล้วเรียนไปด้วย น่าจะเห็นภาพมากกว่า วันนี้เลยลองทำ REST API ง่ายๆ ด้วย Go ดู
ติดตั้ง gorilla/mux
go get -u github.com/gorilla/mux
สร้าง Router
r := mux.NewRouter()
การกำหนด path และ handle function สมมติ ต้องการ ให้ route เรามี url แบบนี้/hello
r.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
})
จากโคีดตัวอย่างของ gowebexamples เรายังสามารถกำหนด methods ได้ ว่ารับเฉพาะ GET, POST, PUT, DELETE หรือเปล่า แบบนี้
r.HandleFunc("/books/{title}", CreateBook).Methods("POST")
r.HandleFunc("/books/{title}", ReadBook).Methods("GET")
r.HandleFunc("/books/{title}", UpdateBook).Methods("PUT")
r.HandleFunc("/books/{title}", DeleteBook).Methods("DELETE")
สามารถ ดึงค่า params ได้จาก request แบบนี้
r.HandleFunc("books/{title}/page/{page}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
vars["title"] // the book title slug
vars["page"] // the page
})
สมมติ path คือ /books/hello-world/page/100
ค่าที่ได้คือ
vars["title"]
- จะได้hello-world
vars["page"]
- จะได้ค่า 100 (เป็น string)
ลองสร้างโปรเจ็คขึ้นมา
ผมสร้างโปรเจ็คใหม่ขึ้นมา เพื่อลองทดสอบการทำ API โดยจะ response เป็น application/json
GET /
- เป็น Home ไม่มีอะไร แค่ แสดง Hello WorldGET /posts
- แสดง posts ทั้งหมดGET /posts/{id}
- แสดง post by idGET /users
- แสดง users ทั้งหมดGET /users/{id}
- แสดง user by id
go init go-basic-api
ต่อมาที่ main.go
ผมสร้าง function ขึ้นมา กำหนด Router และ path ดังนี้
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/posts", PostsHandler)
r.HandleFunc("/posts/{id}", PostHandler)
r.HandleFunc("/users", UsersHandler)
r.HandleFunc("/users/{id}", UserHandler)
log.Fatal(http.ListenAndServe(":8910", r))
}
ซึ่ง handle function ทั้งหมด ยังไม่มีอะไร เพียงแค่ ส่ง text กลับไป
func HomeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
func PostsHandler ...
func UsersHandler ...
func PostHandler ...
func UserHandler ...
เมื่อต้องการให้ response เป็น JSON ผมก็เลยต้องกำหนด struct และ ใช้ encoding/json
สร้าง struct ง่ายๆ ขึ้นมา 2 ตัวคือ User และ Post พร้อมทั้ง กำหนด default values เป็นค่า mock value มั่วๆ
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"`
}
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},
}
กำหนด Handle function ให้ response เป็น JSON เช่น Posts และ Users ก็ใช้ของ encoding/json
ได้เลย แบบนี้
func PostsHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(posts)
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(users)
}
ส่วน PostHandler และ UserHandler ที่ต้องรับ params id
ด้วย ก็จะใช้วิธีนี้
func UserHandler(w http.ResponseWriter, r *http.Request) {
// 1. get id จาก params
vars := mux.Vars(r)
id := vars["id"]
var user User
// 2. วน loop หา ค่าใน users ที่ id ตรงกับ params.id
for _, u := range users {
if u.ID == id {
user = u
break
}
}
// 3. return json user ที่เจอกลับไป
json.NewEncoder(w).Encode(user)
}
สุดท้ายผมสังเกต มัน return JSON กลับมาแหละ แต่ว่า ตัว content-type จริงๆ เป็น text/plain
ไม่ได้เป็น application/json
ก็เลย ต้องเปลี่ยน ให้มันเป็น json ด้วย
w.Header().Add("Content-Type", "application/json")
ซึ่งมันต้องมาใส่ ทุกๆ handler function คิดว่า ไม่น่าจะดี เลยคิดว่า มันมีเหมือน node.js middleware หรือเปล่านะ?
ได้คำตอบว่า มี และใช้งานคล้ายๆ กันเลย คือผมใช้เป็นแบบนี้
r := mux.NewRouter()
r.Use(commonMiddleware)
ส่วนตัว commonMiddleware
ก็คือ set header content-type
func commonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
สุดท้าย ก็ได้ REST API ด้วย golang ง่ายๆ แล้วครับ ตัว main.go
ที่เรียนวันนี้คือ
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
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"`
}
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},
}
func main() {
r := mux.NewRouter()
r.Use(commonMiddleware)
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/posts", PostsHandler)
r.HandleFunc("/posts/{id}", PostHandler)
r.HandleFunc("/users", UsersHandler)
r.HandleFunc("/users/{id}", UserHandler)
log.Fatal(http.ListenAndServe(":8910", r))
}
func commonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
func PostsHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(posts)
}
func PostHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
var post Post
for _, p := range posts {
if p.ID == id {
post = p
break
}
}
json.NewEncoder(w).Encode(post)
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(users)
}
func UserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
var user User
for _, u := range users {
if u.ID == id {
user = u
break
}
}
json.NewEncoder(w).Encode(user)
}
FYI: โดยในตัวอย่าง ผมยังไม่ได้ กำหนด http methods นะครับ ฉะนั้น มันก็จะรับทุกๆ http methods ขอเพียงแค่ resource url เดียวกัน เช่น DELETE ก็ได้ response เดียวกันกับ GET
curl -X DELETE http://localhost:8910/users
# ได้ result เดียวกันกับ GET
[{"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}]
Source Code
Happy Coding ❤️
References
