Qiitaの情報をフル活用しながら初めてGoで簡単なAPI実装した過程を記す


この記事は[Go Advent Calendar 2019](https://qiita.com/advent-calendar/2019/go) 16日目の記事になります

実務で初めてGoを使うことに決めました。

Go自体は全3回のチュートリアル数年前に受け(当時はGoLandがまだ無料で使えた)、
それ以来全く触っていませんでした。
`スライス` が大事と教わった気がします。

Goの仕様を思い出しながら実装していきます。
何かしらの情報を取得するAPIを実装しながら、思ったことを書いていこうと思います。

やりたい箇所はこんな感じ

- 開発環境構築
- ルーティング設定
- GETリクエストを受信
- ORMでDB接続(MySQL)、データ取得
- jsonでレスポンスを返す

# 「Go API」で検索

さて、「Go API」でとりあえずQiita検索

最初にこちらがヒットしました。
[Go のAPI開発](https://qiita.com/gonza_kato_atsushi/items/dce685a9470e5b9f52b3)
なるほど、ORMやAPIフレームワーク、確認ツールの名前が書いてあります。
実装やフレームワーク選定したいのでもう少し情報を探します。

おお！**惹かれるタイトル**が出てきました！
[Goで超簡単API](https://qiita.com/k-penguin-sato/items/8088b69304ee7e8f70be)
これ読めば、そのまま出来ちゃいますね！
ルーティングは[gorilla/mux](https://github.com/gorilla/mux)を使って設定する方法の様です。
とても使いやすそうですね。

[Gorillaシリーズ](http://www.gorillatoolkit.org/)はtoolkitで色んな種類があるんですね。

使うタイミングで色々調べたい。宿題が一つ出来ました。
[gorilla/context] (http://www.gorillatoolkit.org/pkg/context)
[gorilla/mux](http://www.gorillatoolkit.org/pkg/mux)
[gorilla/reverse] (http://www.gorillatoolkit.org/pkg/reverse)
[gorilla/rpc](http://www.gorillatoolkit.org/pkg/rpc)
[gorilla/schema](http://www.gorillatoolkit.org/pkg/schema)
[gorilla/securecookie] (http://www.gorillatoolkit.org/pkg/securecookie)
[gorilla/sessions] (http://www.gorillatoolkit.org/pkg/sessions)
[gorilla/websocket] (http://www.gorillatoolkit.org/pkg/websocket)

さて、もう少し他の記事も見てみます..。
[GoでAPIをシュッと書く](https://qiita.com/sys_cat/items/80f1d8adf7eea31010a6)
おお、これも惹かれますね..!!
こちらは[echo](https://echo.labstack.com/)というWebフレームワークを使っているようです。
これも色々付いてて便利そうですね。
CORSとかCSRFとかの対策はフレームワークの機能で実装出来るならそうしたいですね。

APIで検索すると色んなフレームワークでの実装例が出てきますね...
フレームワークを使うならまずフレームワークを決めようと思います。

# 「go api framework」 で検索

[Go言語Webフレームワークランキング](https://qiita.com/loftkun/items/1a9951d1864bebdc51e1)が出てきました！
最高です！迷ったらランキングですよね。(安易)

`使ってる人が多い=情報が多い`というのは一つの優位性でもあると思います。

**Webフレームワーク**
→[gin](https://github.com/gin-gonic/gin)というフレームワークが人気のようです。
APIのためにWEBフレームワークを入れるかどうかは作りたいアプリ次第という感じでしょうか。

**Restフレームワーク**
→[go-json-rest](https://github.com/ant0ine/go-json-rest)が一番人気。
これは期待するフレームワークのような気がしますね...!

**router**
→[julienschmidt/httprouter](https://github.com/julienschmidt/httprouter)が人気のようですね。[gorilla/mux](https://github.com/gorilla/mux)は２位でした。router提供のフレームワークは多いようです。

**マイクロサービス**
→[go-kit/kit](https://github.com/go-kit/kit)が人気のようです。Goといえばマイクロサービスというイメージがあります。唯一知っていた`goa`も3位にいました。このカテゴリに入るんですね。。

さて、人気ランキングを見た結果、今回は `go-json-rest`で作ってみることにしました。
シンプルなAPIが作れそうです。
`gin` も使ってみたいので、それはまた宿題です。。

# ant0ine/go-json-restを使ってAPIの実装

恐らく、過去幾多の記事が上がっているであろうフレームワークでAPIを作るということをやっていきます。
みんな最初は通る道ですよね。そしてここからは公式ドキュメントを見ながら進めていきます。

[公式ドキュメント](https://github.com/ant0ine/go-json-rest)をしっかり読みます。

## 開発環境構築

```
go get github.com/ant0ine/go-json-rest/rest
```

すんなり入りました。

goの環境は。以下のコマンドで確認した所、
期待した設定になっていたので、以前学習した環境が残っていたようです。
mac環境です。

```
$ go env GOROOT

結果
$ /usr/local/go
```

```
$ go env GOPATH

結果
$ /Users/(ユーザー名)/go
```

## Hello world確認

公式ドキュメントにHello world確認のコードがあったので早速使います。

ようやくGoのコードが...

```go:main.go
package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
```

`NewApi()`でobjectをまず作ると。まぁ、そうですよね。
`api.Use(rest.DefaultDevStack...)`
[DefaultDevStack](https://github.com/ant0ine/go-json-rest/blob/ebb33769ae013bd5f518a8bac348c310dea768b8/rest/api.go#L51)はmiddlewareを定義しているようです。
`...` は可変引数ですかね。
`api.SetApp`で受信待ち状態になるようです！
リクエストを受けるとjsonで返してくれそうです。

```
go run main.go
```

これで待ち状態になりました。
別ターミナルでcurlを叩きます。

```
curl -i http://127.0.0.1:8080/
```

レスポンスが返ってきました！Hello World!です。

```
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Mon, 16 Dec 2019 11:02:56 GMT
Content-Length: 28

{
  "Body": "Hello World!"
}
```

go run している方も受信ログが出ています。

```
16/Dec/2019:20:02:56 +0900 200 80μs "GET / HTTP/1.1" - "curl/7.64.1"
```

とても簡単に動きました。

## ルーティング設定

さて、次に必要なのはルーティング。
[MakeRouter](https://github.com/ant0ine/go-json-rest/blob/ebb33769ae013bd5f518a8bac348c310dea768b8/rest/router.go#L21)を使うと良さそうです！

```go:main.go
import (
	"log"
	"net/http"

	"github.com/ant0ine/go-json-rest/rest"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/hello", func(w rest.ResponseWriter, r *rest.Request) {
			w.WriteJson(map[string]string{"Body": "Hello World!"})
		}))
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)

	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
```

これで`/hello` の時にHello Worldが返るはずです。

```
$ curl -i http://127.0.0.1:8080/hello
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Mon, 16 Dec 2019 12:13:36 GMT
Content-Length: 28

{
  "Body": "Hello World!"
}
```



さて、仕組みは出来たので、今度はORMも使ってDB接続の処理も追加していきます。
ORMは[jinzhu/gorm](https://github.com/jinzhu/gorm)を使ったやり方がドキュメントに載っています。
早速使ってみます。

mysqlのドライバとGORMをインストールします！

```
go get github.com/go-sql-driver/mysql

go get github.com/jinzhu/gorm
```

# ORMでDB接続(MySQL)、データ取得

最後、一気に書きます！

```go:main.go
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/ant0ine/go-json-rest/rest"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

func main() {
	i := Impl{}
	i.InitDB()
	i.InitSchema()
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/messages", i.GetAllUserMessages),
		rest.Post("/messages", i.PostUserMessages),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type UserMessage struct {
	Id        int64     `json:"id"`
	Message   string    `sql:"size:1024" json:"message"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
	DeletedAt time.Time `json:"-"`
}

type Impl struct {
	DB *gorm.DB
}

func (i *Impl) InitDB() {
	var err error
	i.DB, err = gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true")
	if err != nil {
		log.Fatalf("Got error when connect database, the error is '%v'", err)
	}
	i.DB.LogMode(true)
}

func (i *Impl) InitSchema() {
	i.DB.AutoMigrate(&UserMessage{})
}

func (i *Impl) GetAllUserMessages(w rest.ResponseWriter, r *rest.Request) {
	userMessages := []UserMessage{}
	i.DB.Find(&userMessages)
	w.WriteJson(&userMessages)
}

func (i *Impl) PostUserMessages(w rest.ResponseWriter, r *rest.Request) {
	userMessages := UserMessage{}
	if err := r.DecodeJsonPayload(&userMessages); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := i.DB.Save(&userMessages).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteJson(&userMessages)
}
```

上記処理の説明。
まずは、初期処理でDBに接続を行います。

```go
	i.InitDB()
	i.InitSchema()
```

以下でMySQLの設定を書いています。
下記の場合rootユーザーでパスなし、`test`というDB名で接続している。

```go
gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true")
```

以下で、DBにtableがなければmigrationで作成する処理を呼んでいます。
定義は構造体がベースになります(びっくり)

```go
i.DB.AutoMigrate(&UserMessage{})

// 構造体
type UserMessage struct {
	Id        int64     `json:"id"`
	Message   string    `sql:"size:1024" json:"message"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
	DeletedAt time.Time `json:"-"`
}
```

以下で`messages`の`GET`と`POST`そこに紐づくメソッドを定義しました。

```go
	router, err := rest.MakeRouter(
		rest.Get("/messages", i.GetAllUserMessages),
		rest.Post("/messages", i.PostUserMessages),
	)
```

GETの時は、DBから取得した値を書き、POSTの際はRequestの内容をDBに設定します。

```go

func (i *Impl) GetAllUserMessages(w rest.ResponseWriter, r *rest.Request) {
	userMessages := []UserMessage{}
	i.DB.Find(&userMessages)
	w.WriteJson(&userMessages)
}

func (i *Impl) PostUserMessages(w rest.ResponseWriter, r *rest.Request) {
	userMessages := UserMessage{}
	if err := r.DecodeJsonPayload(&userMessages); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := i.DB.Save(&userMessages).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteJson(&userMessages)
}

```

以下で、POSTしてレコードセットします。

```
$ curl -i -H 'Content-Type: application/json' -d '{"Message":"this is a test"}' http:/127.0.0.1:8080/messages
```
以下で、GETしてDBからレコード取得してjsonでresponseを返します。

```
$ curl -i http://127.0.0.1:8080/messages
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Mon, 16 Dec 2019 13:42:32 GMT
Content-Length: 138

[
  {
    "id": 1,
    "message": "this is a test",
    "createdAt": "2019-12-16T00:00:00Z",
    "updatedAt": "2019-12-16T00:00:00Z"
  }
]
```

これで目的は達成できました！

-  開発環境構築
-  ルーティング設定
-  GETリクエストを受信
-  ORMでDB接続(MySQL)、データ取得
-  jsonでレスポンスを返す


# まとめ
Goのリハビリとして、凄く簡単なことを長々と書いてしまいましたが、
Goの情報はかなり豊富にネット上にあるため、学習コストは低く感じます。
さらに内容は今回のような初歩的な内容から深いコアな内容まで幅広くあると思うので、


今回の内容ももっと実務的な内容やコードに寄せて行けるように、
定期的に続編を書いていければと思います。