【はじめてのGO】gin + gormでシンプルなCRUDなREST APIを作成する

GO言語の勉強をはじめてまだ1ヶ月もないのですが、シンプルにCRUDするAPIを作りたいと思い、Go言語でのAPIの作り方(この記事ではginを使います)、DB接続、そしてcurlで叩いてCRUDするまでの過程をなぞっていきます。

# 使用するライブラリ
- gin ・・・ Go言語でのWAF(Web Application Framework)。API作るのに使います。
- gorm ・・・ORM。DBとのやりとりで使います。
- json ・・・ 標準ライブラリですが一応。marshal/unmarshalメソッドを用いてjsonと構造体の変換を担います。

# 各種ライブラリを試す
CRUD APIの作成に向けて、上記3つのライブラリについて先に簡易な説明をしていきます。
## gin
ginはGo言語のWAFです。他にもGO言語には様々なWAFがあるようなのですが、ginの記事が多くあったので今回はこれを使ってみました。
試しに、Hello Worldを返すだけの簡易なAPIを作ってみます。
ますは、インストール。

```shell
$ go get github.com/gin-gonic/gin
```

以下のようにポート8080で受け取るように作成します。

```golang
package main

import (
 "net/http"

 "github.com/gin-gonic/gin"
)

func main() {
 r := gin.Default()

 r.GET("/hello", func(c *gin.Context) {
  c.String(http.StatusOK, "Hello world")
})

 r.Run(":8080")
}
```

実行し、curlで叩いてみましょう。

```shell
$go run main.go
[GIN-debug] GET    /hello     --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
```

この状態でもう一つターミナルを立ち上げて、curlで叩きます。

```shell
$ curl localhost:8080/hello
Hello world
```

バッチリ返ってきました。こんな風にしてAPIを作っていきます。

## gorm
次はORMであるgormです。
gormを使う前に、db接続の部分をみてみましょう。

```golang
package main

import (
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

func gormConnect() *gorm.DB {
	DBMS := "mysql"
	USER := <DB user>
	PASS := <DB Password>
	PROTOCOL := tcp(<DBのIPアドレス>:<PORT>)
	DBNAME := <DB NAME>
	CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME
	db, err := gorm.Open(DBMS, CONNECT)

	if err != nil {
		panic(err.Error())
	}
	fmt.Println("db connected: ", &db)
	return db
}

func main() {
 db := gormConnect()

 defer db.Close()
 db.LogMode(true)
}
```

接続ができたら、次はmigrationです。usersテーブルを作成します。
usersのモデルを作成します。

```golang
type User struct {
 gorm.Model
 Name     string    `json:"name"`
 Age      int       `json:age`
 Birthday time.Time `json:birthday`
}
```

`gorm.Model` はベースとなるモデル定義で、IDやCreatedAtなどを自身のモデルに埋め込めます。
http://doc.gorm.io/models.html#model-definition

main.goに下記を追記します。

```golang
db.Set("gorm:table_options", "ENGINE=InnoDB")
db.AutoMigrate(&User{})
```

実行すると、mysqlにusersテーブルが作成されます。

```sql
mysql> show columns from users;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
| deleted_at | timestamp        | YES  | MUL | NULL    |                |
| name       | varchar(255)     | YES  |     | NULL    |                |
| age        | int(11)          | YES  |     | NULL    |                |
| birthday   | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
```

SELECTやUPDATE等については他にもたくさん記事があるので、そちらを参考にしました。
参考にした記事：
[【GORM】Go言語でORM触ってみた](https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81)
[Go言語のGORMを使ってみた①](https://qiita.com/gorilla0513/items/27cd34433a48fc8b65db)

記事の下部に最終的なCRUD APIのソースを置くので、詳細はそちらをご参照ください。

## json(Marshal/Unmarshal)
次は、jsonと構造体の変換についてです。
encoding/jsonパッケージのMarshal/Unmarshalというメソッドを用います。

### Unmarshal
jsonから構造体に変換します。
まずは、jsonファイルを作成します。

```json:test.json
[
  {
    "name": "Yamada",
    "age": 22,
    "birthday": "1996-08-06T00:00:00+09:00"
  }
]
```

次に、このjsonファイルを読み込み、Unmarshalします。Unmarshalは構造体に記載したjsonタグに対応したフィールドにマッピングされます。

```golang
//jsonファイルの読み込み
func main() {
 jsonBytes, err := ioutil.ReadFile("test.json")
 if err != nil {
 	log.Fatal(err)
 }
 //json(bytes)→構造体 
 users := []User{}
 if err := json.Unmarshal(jsonBytes, &users); err != nil {
 	fmt.Println("Unmarshal error:", err)
 	return
 }
 fmt.Println("user name ->", users[0].Name) //YAMADA
 fmt.Println("user age->", users[0].Age) //22
 fmt.Println("user birthday->", users[0].Birthday) //1996-08-06 00:00:00 +0900 JST
}
```

### Marshal
次は反対に構造体→jsonをやってみます。

```golang
func main() {
 //User型の構造体→json
 user := User{
  Name:     "Itou",
  Age:      10,
  Birthday: time.Date(2009, 2, 19, 12, 0, 0, 0, time.Local),
 }

 jsonBytes, err := json.Marshal(user)
 if err != nil {
  fmt.Println("Marshal error:", err)
  return
 }

 fmt.Println("user ->", string(jsonBytes))
}
```

結果は以下のようになります。

```shell
$ go run main.go 
user -> {"ID":0,"CreatedAt":"0001-01-01T00:00:00Z","UpdatedAt":"0001-01-01T00:00:00Z","DeletedAt":null,"name":"Itou","Age":10,"Birthday":"2009-02-19T12:00:00+09:00"}
```

必要なものは整ったので、早速APIを作っていきましょう！

# CRUDのREST APIを作成する
## CREATE
まずはCREATEから。
CreaetdAtやUpdatedAtはバック側でtimeを用いて追記するようにしました。

```golang
//CREATE
r.POST("/user", func(c *gin.Context) {
 user := User{}
 now := time.Now()
 user.CreatedAt = now
 user.UpdatedAt = now

 err := c.BindJSON(&user)
 if err != nil {
   c.String(http.StatusBadRequest, "Request is failed: "+err.Error())
 }
 db.NewRecord(user)
 db.Create(&user)
 if db.NewRecord(user) == false {
  c.JSON(http.StatusOK, user)
 }
})
```

無事に登録されれば、jsonで作成したレコードを返すようにしています。
curlで叩いみると正しく帰ってきているのがわかります。

```shell
$ curl localhost:8080/user curl -X POST -H "Content-Type: application/json" -d '{"name":"sensuikan1973", "age":15, "birthday": "2007-02-04T03:18:45.000Z"}'
{"ID":2,"CreatedAt":"2019-02-23T23:28:48.832877+09:00","UpdatedAt":"2019-02-23T23:28:48.832877+09:00","DeletedAt":null,"name":"sensuikan1973","age":15,"birthday":"2007-02-04T03:18:45Z"}
```

また、以下を記載しておくと、実際に発行されたクエリを確認できるので記載しておきましょう。

```golang
db.LogMode(true)
```

## READ
次はデータの取得です。全データ取得と1レコードの取得でやってみます。

```golang
//READ
//全レコード
r.GET("/users", func(c *gin.Context) {
 users := []User{}
 db.Find(&users) // 全レコード
   c.JSON(http.StatusOK, users)
})
//1レコード
r.GET("/user/:id", func(c *gin.Context) {
 user := User{}
 id := c.Param("id")

 db.Where("ID = ?", id).First(&user)
 c.JSON(http.StatusOK, user)
})
```

全レコードはFindを用いて配列で取得、1レコードの例はidをparamから拾うようにし、Whereで指定した後、Firstで最初の1レコードだけを出すようにしています。

## UPDATE
次はUpdateです。idをparamsから受け取り、更新するデータはjsonで取得しています。

```golang
//UPDATE
r.PUT("/user/:id", func(c *gin.Context) {
 user := NewUser()
 id := c.Param("id")

 data := NewUser()
 if err := c.BindJSON(&data); err != nil {
   c.String(http.StatusBadRequest, "Request is failed: "+err.Error())
 }

 db.Where("ID = ?", id).First(&user).Updates(&data)
})
```

## DELETE
gormでは、削除フラグ `deleted_at` を立て、DELETE()を用いることで自動で論理削除として削除を実行してくれます。

```golang
//DELETE
r.DELETE("/user/:id", func(c *gin.Context) {
 user := NewUser()
 id := c.Param("id")

 db.Where("ID = ?", id).Delete(&user)
})
```

# おわりに
今回、GoでCRUDなRESTful APIを作成するために、便利なライブラリとしてginとgormの説明、そして実際の作成を行いました。
gormの仕組みは非常にわかりやすかったのですが、クエリが複雑になってくると記載が難しそうな気がしました。

今回のソースは以下に置いています。
まだ初学者ゆえ、修正点等あればご連絡頂けると幸いです。
https://github.com/daitasu/go-crud-first/blob/master/main.go

## 参考記事
記事中にもいくつか記載しましたが、以下を参考にさせて頂きました。
[【GORM】Go言語でORM触ってみた](https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81)
[Go言語のGORMを使ってみた①](https://qiita.com/gorilla0513/items/27cd34433a48fc8b65db)
[Go言語製WAF GinでWebアプリを作ってみる](https://medium.com/eureka-engineering/go%E8%A8%80%E8%AA%9E%E8%A3%BDwaf-gin%E3%81%A7web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B-%E6%BA%96%E5%82%99%E7%B7%A8-6f14aba0da29)
[gorm Doc](http://doc.gorm.io/crud.html)