gormとgo-json-restを使ってDBからのデータを返すAPIサーバを作る

この記事は、[Treasure Advent Calendar 2018](https://qiita.com/advent-calendar/2018/treasure)の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です。もう勝手に動いている体で話します。

## ライブラリのインストール
```shell
$ go get github.com/jinzhu/gorm
$ go get github.com/ant0ine/go-json-rest
```

## とりあえず全部のソースコード


今回、DBにはcomicsテーブルにマンガの情報が入っていて、
そのデータを全部引っ張り出して来るのと、
Query Parameterで引き出してくる数を指定できると言った仕様のAPIである。


```go:main.go
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`
とか書いていただくと、ちゃんと動いてくれます。

## 少し説明
いらぬお世話かも知れないが、ちょっとだけコードの説明を挟んでおきます。
自分が初めてプログラミングをした時のことを思い出すと、理解できていないと思うので、少しだけどういうことをしているのか説明しておきます。

上記のコードにコメントを書いておきます。
間違いや補足があったら教えて下さい！！

```go:main.go
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),
		// ここでは１つしか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)
}
```

分かりやすいかどうかはさておき、コメントをたくさんつけておいたので、何かの参考になれば幸いです。

## 最後に
まだまだポンコツエンジニアなので、間違いや、コードがよろしくなかったりすると思うので、ぜひ、ご指摘下さい。
ありがとうございます。