golangでREST APIをやってみた①

# はじめに

この記事は JSL (日本システム技研) Advent Calendar 2018 - Qiita 20日目の記事です。

昨年同様、一人アニバーサリー記念記事です。色々ありましたが、なんとか本厄も乗り切れそうです！

golangでREST APIをやってみた話を書きます。最近ちらほらPythonからgolangへ移行した
話を聞くので、お試し的なノリでやってみたいと思います。

今回は、「ORMで取得したデータをGETメソッドで返すところまで」を目標にします。
弊社のプロダクトでDjango　restframeworkを使用してバックエンドを実装しているものがいくつかあるので、今後は、そちらのAPIとのパフォーマンス比較をしたりしてみたいです。

※golangデビューなので、手探り感満載なので、お気づきの点あればフォローもらえるとありがたいです！！

# 環境周り

* macOS 10.2.6
* go 1.11.2 
* dep : パッケージ管理
* go-json-rest : REST API（https://github.com/ant0ine/go-json-rest)
* gorm : ORM(https://github.com/jinzhu/gorm)

# まずはHello world！

まずは、go-json-restの公式にある「Hello world！」を試してみます。

```golang
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())) 
}
```

実行して、ブラウザから　「http://127.0.0.1:8080」 を叩いて見ます。

```
$ go run helloworld.go
```

"Body": "Hello World!"が返されたことを書くに出来ました。

# REST APIを試す

同じく、go-json-restの公式にある「Countries」を試してみます。
サンプルコードは、DBで永続化はせずに、メモリ上にデータを置く感じです。

```golang
package main

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

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/countries", GetAllCountries),
		rest.Post("/countries", PostCountry),
		rest.Get("/countries/:code", GetCountry),
		rest.Delete("/countries/:code", DeleteCountry),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}


type Country struct {
	Code string
	Name string
}

var store = map[string]*Country{}

var lock = sync.RWMutex{}


func GetCountry(w rest.ResponseWriter, r *rest.Request) {
	code := r.PathParam("code")

	lock.RLock()
	var country *Country
	if store[code] != nil {
		country = &Country{}
		*country = *store[code]
	}
	lock.RUnlock()

	if country == nil {
		rest.NotFound(w, r)
		return
	}
	w.WriteJson(country)
}

func GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
	lock.RLock()
	countries := make([]Country, len(store))
	i := 0
	for _, country := range store {
		countries[i] = *country
		i++
	}
	lock.RUnlock()
	w.WriteJson(&countries)
}

func PostCountry(w rest.ResponseWriter, r *rest.Request) {
	country := Country{}
	err := r.DecodeJsonPayload(&country)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if country.Code == "" {
		rest.Error(w, "country code required", 400)
		return
	}
	if country.Name == "" {
		rest.Error(w, "country name required", 400)
		return
	}
	lock.Lock()
	store[country.Code] = &country
	lock.Unlock()
	w.WriteJson(&country)
}

func DeleteCountry(w rest.ResponseWriter, r *rest.Request) {
	code := r.PathParam("code")
	lock.Lock()
	delete(store, code)
	lock.Unlock()
	w.WriteHeader(http.StatusOK)
}
```

curlコマンドで以下のような感じで叩きます。

```
# POST
$ curl -i -H 'Content-Type: application/json' \
 -d '{"Code":"JP","Name":"Japan"}' http://127.0.0.1:8080/countries

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Tue, 18 Dec 2018 05:52:20 GMT
Content-Length: 37

{
  "Code": "JP",
  "Name": "Japan"
}

# GET
$ curl -i http://127.0.0.1:8080/countries/JP

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Tue, 18 Dec 2018 05:53:45 GMT
Content-Length: 37

{
  "Code": "JP",
  "Name": "Japan"
}


$ curl -i http://127.0.0.1:8080/countries

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Tue, 18 Dec 2018 05:56:39 GMT
Content-Length: 104

[
  {
    "Code": "JP",
    "Name": "Japan"
  },
  {
    "Code": "US",
    "Name": "United States"
  }
]⏎                                                       ⏎                                                                                                                
```

# ORMを試す

なんとなくRESTぽいことが出来ることが分かったの、実際に既存のMySQLよりでORMでデータ取得してみます。まずはお試しなので、Django標準で作成されるテーブル 
**auth_user** より全件取得してみました。

注意点として、**GORM** は、デフォルトだとModel名に対して、「s」を付与したテーブル名を探しにいくため（例：UserであればUsersを探しにいく）、今回のように既存のテーブルを読む場合は、別途 **TableName()**というメソッドを用意してあげる必要があります。

```golang
package main

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"fmt"
)

type User struct {
	ID      int64 `gorm:"primary_key"`
	Username  string
}

func (s *User) TableName() string {
	return "auth_user"
}

func gormConnect() *gorm.DB {
	DBMS     := "mysql"
	USER     := "user"
	PASS     := "password"
	PROTOCOL := "tcp(127.0.0.1:3306)"
	DBNAME   := "dbname"
  
	CONNECT := USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME
	db,err := gorm.Open(DBMS, CONNECT)
  
	if err != nil {
	  panic(err.Error())
	}
	return db
}
  
func main() {
	db := gormConnect()

	// 全件取得
	var allUsers []User
	db.Find(&allUsers)
	fmt.Println(allUsers)

	defer db.Close()
}
```

実行すると以下のようなフォーマットで取得することが出来ました。

```
$ go run gorm.go
[{1 admin} {2 katekichi} {3 nakazawa} {4 hogefuga} {6 fugahoge}]
```

# ORMで取得したデータをGETで返す

ORMから取得する箇所は、先ほどのものと同じです。


```
package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"fmt"
)

type User struct {
	ID      int64 `gorm:"primary_key"`
	Username  string
}

func (s *User) TableName() string {
	return "auth_user"
}

func gormConnect() *gorm.DB {
	DBMS     := "mysql"
	USER     := "user"
	PASS     := "password"
	PROTOCOL := "tcp(127.0.0.1:3306)"
	DBNAME   := "dbname"
  
	CONNECT := USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME
	db,err := gorm.Open(DBMS, CONNECT)
  
	if err != nil {
	  panic(err.Error())
	}
	return db
}

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/users", GetAllUsers),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

func GetAllUsers(w rest.ResponseWriter, r *rest.Request) {
	db := gormConnect()
	defer db.Close()

	// 全件取得
	var allUsers []User
	db.Find(&allUsers)
	fmt.Println(allUsers)

	w.WriteHeader(http.StatusOK)
	w.WriteJson(&allUsers)
}
```

curlコマンドで叩きます。

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

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Wed, 19 Dec 2018 04:47:35 GMT
Content-Length: 245

[
  {
    "ID": 1,
    "Username": "admin"
  },
  {
    "ID": 2,
    "Username": "katekichi"
  },
  {
    "ID": 3,
    "Username": "nakazawa"
  },
  {
    "ID": 4,
    "Username": "hogefuga"
  },
  {
    "ID": 6,
    "Username": "fugahoge"
  }
]⏎                                                   
```

# まとめ

今回は、時間の都合上、GETの実装のみで終わってしまいましたが、引き続き、その他のメソッドについても実装したいです。また、ページネーションが非対応のためこの辺りも次回は、触れてみたいと思います。