Echo+GORMでRESTfulなAPIサーバをつくる

# Overview
小規模なプロダクトにおいてPythonでAPIサーバはしんどい(できるけどソースが美しくない)ので, 全然使ったことないけどGoでAPIサーバを立ててみたい

# Description
普段お仕事では[Django REST Framework](https://www.django-rest-framework.org/), 趣味では[Flask](https://a2c.bitbucket.io/flask/)を使っているんですが, PythonでORマッパー使おうとすると[SQLAlchemy](https://www.sqlalchemy.org/)とか使うハメになってしんどいです.

かといってDjangoを使うのはウェイトが重い...
<font color="lightgray">peeweeとかいういい感じのライブラリもあるっぽいですが</font>

ということでせっかくなのでGoで作ってみます

今回は, 要素を一意に特定できる`id`と文字列`text`を要素に持つPostを定義して, API経由でCRUD操作を行うようなアプリを作ります

```
GET: localhost:1323/posts -> 全postの取得
GET: localhost:1323/posts/:id -> id指定でpost取得
PUT: localhost:1323/posts/:id -> id指定でpost更新
POST: localhost:1323/posts/:id -> id指定でpost作成
DELETE: localhost:1323/posts/:id -> id指定でpost削除
```

# Settings
DockerでGoの開発環境を整えます

とりあえず動かしたいという方は後述のソースをまるっとコピりましょう

環境構築について, こちらの記事がわかりやすいです.
[Golang(Echo) x docker-composeでホットリロード用いた開発
](https://qiita.com/y-ohgi/items/671de11f094f72a058b1)

## ディレクトリ構造
```
.
├── Dockerfile
├── docker-compose.yml
└── src
    ├── main.go
    ├── go.mod
    ├── go.sum
    ├── controller
    │   └── sqlite.go
    └── view
        └── post.go
```

## プログラム本体
ディレクトリ構造に合わせてコピる

### main.go
GET, POST, PUT, DELETEなど各メソッドを使える

```go
package main

import (
    "github.com/labstack/echo"
    view "app/view"
)

func main() {
    e := echo.New()
    initRouting(e)
    e.Logger.Fatal(e.Start(":1323"))
}

func initRouting(e *echo.Echo) {
    e.GET("/posts", view.GetAllPosts)
    e.GET("/posts/:id", view.GetPost)
    e.POST("/posts", view.CreatePost)
    e.PUT("/posts/:id", view.UpdatePost)
    e.DELETE("/posts/:id", view.DeletePost)
}
```

### post.go
`json:id`でレスポンスのjsonのキー名を指定できる. デフォだとPascalCaseになっちゃうのがウザいので設定

`gorm:hoge`で各属性を指定できる

<font color="lightgray">これDB接続のところDecoratorみたいな感じで綺麗に共通化できないんでしょうか...
詳しい方教えて下さい</font>

```go
package view

import (
    "net/http"
    "github.com/labstack/echo"
    . "app/controller"
)

type Post struct {
    Id int `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
    Text string `json:"text"`
}

func GetAllPosts(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    var posts []Post
    db.Find(&posts)
    return c.JSON(http.StatusOK, posts)
}

func GetPost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }
}

func CreatePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    post := new(Post)
    if err := c.Bind(post); err != nil {
        return err
    }
    db.Create(&post)

    return c.JSON(http.StatusOK, post)
}

func UpdatePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()

    newPost := new(Post)
    if err := c.Bind(newPost); err != nil {
        return err
    }

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id).Update(newPost)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }

}

func DeletePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id)
        db.Delete(post)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }
}
```

### sqlite.go
hoge.sqlite3はDBを保存したいパスを指定
`:memory:` の方を使うとインメモリで使える
 
```go
package controller

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

func OpenSQLiteConnection() *gorm.DB {
    //db, err := gorm.Open("sqlite3", ":memory:")
    db, err := gorm.Open("sqlite3", "hoge.sqlite3")
    if err != nil {
        panic("failed to connect database.")
    }
    
    db.LogMode(true)
    return db
}

```

## Docker関連ファイル
libc-dev, gccがないとrealize(ホットリロード用ライブラリ)がインストールできないのでインスコ

### Dockerfile
```dockerfile
FROM golang:1.13.5-alpine as build

WORKDIR /go/app

COPY src .

RUN apk add --no-cache git gcc libc-dev \
 && go build -o app \
 && go get gopkg.in/urfave/cli.v2@master \
 && go get github.com/oxequa/realize

FROM alpine

WORKDIR /app

COPY --from=build /go/app/app .

RUN addgroup go \
  && adduser -D -G go go \
  && chown -R go:go /app/app

CMD ["./app"]
```

### docker-compose.yml
```yaml
version: '3.5'

services:
  app:
    build:
      context: .
      target: build
    volumes:
      - ./src:/go/app
    command: realize start --run --no-config
    ports:
      - 1323:1323
```

# Run

モジュール管理用ファイルの`go.mod`を生成

```
$ docker run --rm -v `pwd`:/go/app -w /go/app golang:1.13.5-alpine go mod init app
```

各ファイルを作り終わったら次のコマンドでイメージのビルド&実行

```
$ docker-compose build
$ docker-compose up

~
app_1  | [02:18:27][APP] : Install started
app_1  | [02:18:28][APP] : Install completed in 0.446 s
app_1  | [02:18:28][APP] : Running..
app_1  | [02:18:28][APP] :    ____    __
app_1  | [02:18:28][APP] :   / __/___/ /  ___
app_1  | [02:18:28][APP] :  / _// __/ _ \/ _ \
app_1  | [02:18:28][APP] : /___/\__/_//_/\___/ v3.3.10-dev
app_1  | [02:18:28][APP] : High performance, minimalist Go web framework
app_1  | [02:18:28][APP] : https://echo.labstack.com
app_1  | [02:18:28][APP] : ____________________________________O/_______
app_1  | [02:18:28][APP] :                                     O\
app_1  | [02:18:28][APP] : ⇨ http server started on [::]:1323
```

コンテナができたらプログラムが実行されます.
ソースコードを変更すると自動で更新されるのがいい感じです!

各メソッドが使えるかも試しておきましょう
curlでもいいですが, 直感的に使える[httpie](https://httpie.org/)が便利です

```bash
# 全件取得(まだ0件)
$ http GET localhost:1323/posts

# 新規作成(idは自動インクリメント)
$ http POST localhost:1323/posts text=hoge
$ http GET localhost:1323/posts

# id=1のtextを更新
$ http PUT localhost:1323/posts/1 text=fuga
$ http GET localhost:1323/posts/1

# id=1を削除
$ http DELETE localhost:1323/posts/1
$ http GET localhost:1323/posts
```

# Tips
`go get`などコマンドを打ちたいときは

```
$ docker ps
```
で実行中のコンテナIDを調べて, 

```
$ docker exec -it {CONTAINER_ID} sh
```

でコンテナ内に入れます

# References
https://github.com/labstack/echo
https://github.com/jinzhu/gorm
[Golang(Echo) x docker-composeでホットリロード用いた開発
](https://qiita.com/y-ohgi/items/671de11f094f72a058b1)