この記事は、Treasure Advent Calendar 2018の9日目の記事です!
はじめに
Go言語(golang)でDB接続からのJSONを返すAPIサーバーをサクッと作ろうと思い、見つけたライブラリたちを使って書いてみました。
【gorm】
https://github.com/jinzhu/gorm
【go-json-rest】
https://github.com/ant0ine/go-json-rest
どちらも、ドキュメントを読んだら丁寧にExampleがたくさんあるし、gormもgo-json-restもそれぞれ検索したら記事が出てくるので、それを見ていただいたら話は早いかも。
ホントにサクッと動かすだけなので、細かいところは何も考えてないです。笑
環境
macOS Mojave (10.14.1)
Golang 1.11.2
Docker 18.06.1
MySQL 8.0
Docker上のMySQLです。もう勝手に動いている体で話します。
ライブラリのインストール
$ go get github.com/jinzhu/gorm
$ go get github.com/ant0ine/go-json-rest
とりあえず全部のソースコード
今回、DBにはcomicsテーブルにマンガの情報が入っていて、
そのデータを全部引っ張り出して来るのと、
Query Parameterで引き出してくる数を指定できると言った仕様のAPIである。
package main
import (
"github.com/ant0ine/go-json-rest/rest"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"log"
"net/http"
)
func main() {
i := Impl{}
i.InitDB()
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/api/comic/list", i.GetAllComics),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
type Impl struct {
DB *gorm.DB
}
type Comic struct {
ID int64 `json:"id"`
IsbnCode int64 `json:"isbn_code"`
Title string `json:"title"`
Author string `json:"author"`
Publisher string `json:"publisher"`
Cover string `json:"cover"`
ReleaseDate string `json:"release_date"`
}
func (i *Impl) InitDB() {
var err error
i.DB, err = gorm.Open("mysql", "{user_name}:{password}@tcp(localhost:3306)/{database_name}?parseTime=true&&loc=Asia%2FTokyo&charset=utf8")
if err != nil {
log.Fatalf("Got error when connect database, the error is '%v'", err)
}
i.DB.LogMode(true)
}
func (i *Impl) GetAllComics(w rest.ResponseWriter, r *rest.Request) {
v := r.URL.Query()
num := v.Get("num")
comics := []Comic{}
i.DB.Limit(num).Find(&comics)
w.WriteHeader(http.StatusOK)
w.WriteJson(&comics)
}
DBのテーブルに合わせて構造体を定義しておき、jsonタグでキー名を書いておきます。
(jsonタグを書かなければ、構造体のメンバ名が使用されます)
(構造体のメンバ名はキャメルケースで、DBのカラム名はスネークケースでも、よしなにしてくれます)
実行
上記のコードを実行すると、APIサーバの完成。
$ go run main.go
サーバが立ち上がった状態で、ブラウザ等から以下のURLを入力すると、comicテーブルの全レコードが返ってくる。
localhost:8080/api/comic/list
なお、返ってくるレコード数を、例えば10個にしたい場合は、
localhost:8080/api/comic/list?num=10
とか書いていただくと、ちゃんと動いてくれます。
少し説明
いらぬお世話かも知れないが、ちょっとだけコードの説明を挟んでおきます。
自分が初めてプログラミングをした時のことを思い出すと、理解できていないと思うので、少しだけどういうことをしているのか説明しておきます。
上記のコードにコメントを書いておきます。
間違いや補足があったら教えて下さい!!
package main
// 使っているパッケージ、ライブラリをここに書きます
import (
"github.com/ant0ine/go-json-rest/rest"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"log"
"net/http"
)
func main() {
// DB周り初期設定
i := Impl{}
// DBとの接続
i.InitDB()
// おまじない、、
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
// ルーティング。URLから、どの処理をするのか分岐させるところ
router, err := rest.MakeRouter(
rest.Get("/api/comic/list", i.GetAllComics),
// ここでは1つしかAPIがないが、例えば、
// rest.POST("/api/comic", i.PostComicData),
// みたいに書けば、処理を分岐させられる
)
// もし、ルーティングにエラーがあれば、ログを吐く
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
type Impl struct {
DB *gorm.DB
}
// DBから取得したデータを格納するための構造体を定義
type Comic struct {
ID int64 `json:"id"`
IsbnCode int64 `json:"isbn_code"`
Title string `json:"title"`
Author string `json:"author"`
Publisher string `json:"publisher"`
Cover string `json:"cover"`
ReleaseDate string `json:"release_date"`
}
func (i *Impl) InitDB() {
var err error
// DBとの接続
i.DB, err = gorm.Open("mysql", "{user_name}:{password}@tcp(localhost:3306)/{database_name}?parseTime=true&&loc=Asia%2FTokyo&charset=utf8")
// parseTime=trueを書いておくと、なんかtime型をいい感じにしてくれるらしい
// locをTokyoにしておくと、時間がズレないらしい
// charsetをutf8で明示的に宣言しておくと、文字化けしなくてよろしいらしい
if err != nil {
log.Fatalf("Got error when connect database, the error is '%v'", err)
}
i.DB.LogMode(true)
}
func (i *Impl) GetAllComics(w rest.ResponseWriter, r *rest.Request) {
// URLのQuery Parameterを取得
// mapされます
v := r.URL.Query()
// ex) localhost:8080/api/comic/list?num=10&hoge=555 みたいなURLだったら
// -> map[num:[10] hoge:[555]]となる。
// mapからnumというキーの値を取り出す
num := v.Get("num")
// -> num = 10
// Comic構造体の配列を宣言(インスタンス)
comics := []Comic{}
// i.DB.Find(&comics)とすると、全部のレコードが出てくる
// Limit(num)を挟むと、出てくるレコード数を指定できる
i.DB.Limit(num).Find(&comics)
// 他の処理を書くときはこのあたりをいじると、別の事ができるようになる
// エラーハンドリングしてないので怒られそう
// 特にエラーがなく、正常のレスポンスをができていることをヘッダーに書いておく
// http.StatusOK = 200
w.WriteHeader(http.StatusOK)
// 取得したデータをJSON形式で書いてレスポンスを返す
w.WriteJson(&comics)
}
分かりやすいかどうかはさておき、コメントをたくさんつけておいたので、何かの参考になれば幸いです。
最後に
まだまだポンコツエンジニアなので、間違いや、コードがよろしくなかったりすると思うので、ぜひ、ご指摘下さい。
ありがとうございます。