GoでREST APIサーバーを作ったときの備忘録
概要
Golangなんもわからない状態からREST APIを提供できるようになったので、その時の備忘録
TL; DR
github.com/julienschmidt/httprouter
を使えば簡単に作成できる
net/http
とは違い、ハンドラにわたす引数が1つ多いのでそこだけ注意
encoding/json
を組み合わせてJSONレスポンスまで作れたよ
完成物
本来は複数ファイルに分かれているけど、わかりやすさのためにまとめた
package main import ( "encoding/json" "fmt" "log" "net/http" "github.com/julienschmidt/httprouter" ) // ------------------ レスポンス作成/レンダー部分 ---------------------- type StatusCode int // レスポンス形式 resultの中に返したいものが含まれる // interface{} はどんな型でも受け付けるくん type Response struct { Status StatusCode `json:"status"` Result interface{} `json:"result"` } // 結果にステータスコードをつけ、JSON化する // []byteはjson.Marshalの返り値 func CreateJSON (data interface{}, status StatusCode) []byte { response := Response{status, data} res, err := json.Marshal(response) if err != nil { log.Panic(err) } return res } // 200のときはステータスコードを省略できる func RenderJSONOK(w *http.ResponseWriter, data interface{}) { RenderJSON(w, data, http.StatusOK) } // レスポンスの原型を受け取り、JSON形式で返す func RenderJSON(w *http.ResponseWriter, data interface{}, status StatusCode) { res := CreateJSON(data, status) (*w).Header().Set("Content-Type", "application/json") (*w.)WriteHeader(status) (*w).Write(res) } // --------------------------------------------------------------------- // ------------------------ APIメソッド部分 ---------------------------- func GetMessageList(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { RenderJSONOK(&w, "Call GetMessageList!") // -> { "status": 200, "result": "Call GetMessageList!" } } func PostMessage(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { RenderJSON(&w, "coming soon", http.StatusNotFound) // -> { "status": 404, "result": "comming soon" } } func GetMessageById(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { RenderJSON(&w, "coming soon", http.StatusNotFound) // -> { "status": 404, "result": "comming soon" } } // ------------------------------------------------------------------- // -------------------- ルーティング登録部分 -------------------------- // ルーティング登録を切り出して見やすくしたつもり func Registration(router *httprouter.Router) { router.GET("/message", GetMessageList) router.POST("/message", PostMessage) router.GET("/message/:id", GetMessageById) } func main() { router := httprouter.New() Registration(router) err := http.ListenAndServe(":8080", router) if err != nil { log.Panic(err) } else { fmt.Println("something wrong") } }
解説
httprouterを使う
net/http
だけでも、メソッドごとに分割して作れなくも無いっぽいが
func Test(w http.ResponseWriter, r *http.Request) { // メソッドチェック switch (r.Method) { case: http.MethodGet: // GET処理 case: http.MethodPost // POST処理 } } func Registration() { http.HandleFunc("/", Test) // GET, POST, PUT }
このように記述することになる。 個人的には、Registrationの中で一発でメソッドを管理できないなど可読性が悪く感じるのでなし。
そこで、調べていたところ、12kスターが付いている github.com/julienschmidt/httprouter
を見つけた。
これを使うと、完成コードのように、
func TestGet(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // GET処理 } func TestPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // POST処理 } func Registration(router *httprouter.Router) { router.GET("/", TestGet) router.POST("/", TestPost) }
と、メソッドごとに処理を分けて記述することができる。
しかし、httprouterの基本機能として、Paramsが渡ってくる場所が別にあるため、これを受け取れるようにする必要がある。
ここまでで、自由にREST APIを作れるようになった。 ここからは、APIのレスポンスでおなじみのJSONを返すように少してを加える。
encoding/jsonを使う
encoding/json
は標準で搭載されている。
構造体に一手間加えると、JSONを返せるようになる便利パッケージ。
使い方は以下の通り
// `json:""` を忘れると、Hoge, Fugaなど1文字目が大文字になる(属性名そのままになる)ので注意 type Response struct { Hoge int `json:"hoge"` Fuga string `json:"fuga"` } func CreateJSON () []byte { response := Response{123, "abc"} json, err := json.Marshal(response) if err != nil { panic(err) } return json // {"hoge": 123, "fuga": "abc"} }
これで手軽にJSON形式を生成できるので、これをhttpレスポンスにのせて上げればもう完成。 それで、あちこち関数化などをして出来上がったのが上のコード。
まとめ
便利パッケージがある! 名前わかりにくすぎる! リファレンスちゃんと読まないと型が特定できないからドキュメントなどはちゃんと見よう!