golangで簡単なWebAPIを作成してみた

# はじめに
今回はgoogle大先生の作成した言語であるgo言語(golang)を使って簡単なWebAPIを作成してみました。

ソースコードは<a href="https://github.com/VVattaAA-dr/go_webapi">こちら</a>

この記事の対象としてはgo言語で手っ取り早くアプリを作ってみたい人、go言語でデータベースとの通信を行うAPIを作成したい人などを想定しています！

# 参考にした記事
* GoでWebアプリを作ろう 第一回 : Goで簡単なCRUD: http://studio-andy.hatenablog.com/entry/go-todo-crud#f-947194dd
* Golang製DBマイグレーションツールgoose + MySQLを試してみた : https://qiita.com/K_ichi/items/b9362e3a3c5688e494e2

# 工数
2h * 3 = 6hくらい？

# 前提条件
* os - macOS(Mojave 10.14.6)
* framework - gin, xo(あとでインストール)
* migration tool - goose(あとでインストール)
* sql - mysql

# 事前準備
必要に応じて読み飛ばしてください

### goのインストール
自分はgoのインストールも済んでいなかったのでhomebrewでgoをインストール

```bash
$ brew install go
```
homebrewが入っていない人はhomebrewをまずインストール
(参考： https://qiita.com/pypypyo14/items/4bf3b8bd511b6e93c9f9)

### GOPATHの設定
go言語ではgoのライブラリやモジュールのインストール先の親となるディレクトリを`GOPATH`として環境変数に設定しなければならないので設定していきます。
~/goに設定するのがオーソドックスなようなので、自分もホームディレクトリ下にgoというディレクトリを作成し、`GOPATH`に設定しました。 

```(~/.bash_profile)
...
export GOPATH=/Users/wataru_tajima/go
export PATH=$GOPATH/bin:$PATH
```

sourceコマンドで変更を反映します。

```bash
$ source ~/.bash_profile
```

### プロジェクトの作成
GOPATHという特殊な設定のため、プロジェクトの置き場所に迷う方がいらっしゃるかもしれませんが(自分は迷いました笑)、設定したGOPATH経由でインストールしたモジュールやライブラリをどこからでも参照できるのでプロジェクトの置き場所はどこでも大丈夫です。

プロジェクト構成は以下の通りです。

```
go_webapi_sample
├── db
│   ├── dbconf.yml
│   └── migrations
│       └── createTask.sql
├── main.go
└── src
    ├── controller
    │   └── task.go
    └── model
        ├── model.go
        └── task.go
```

# 手順

### DBの作成

```bash
$ mysql -u <USER_NAME> -p <PASSWORD>
# DBのユーザー作成過程は省きます

mysql> create database <DB_NAME>;
```

マイグレーションツールのgooseをインストールし、confファイルをコピーして作成します。

```bash
$ go get bitbucket.org/liamstask/goose/cmd/goose

go_webapi_sample $ mkdir db 
go_webapi_sample $ cd db
go_webapi_sample $ cp $GOPATH/src/bitbucket.org/liamstask/goose/db-sample/dbconf.yml ./
```

dbconf.ymlのdevelopmentの部分のみ以下のように書き換えます。

```yml
# db/dbconf.yml
...
development:
    driver: mymysql
    open: tcp:localhost:3306*<DB_NAME>/<USER_NAME>/<PASSWORD>
...
```

疎通の確認(以下のように表示されればOK)

```bash
go_webapi_sample $ goose status
goose: status for environment 'development'
    Applied At                  Migration
    =======================================
```

taskテーブルを作成します。

```bash
go_webapi_sample $ goose create create_task sql
```

db/migrations/createTask.sqlを以下のように編集します。

```sql
# db/migrations/createTask.sql

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE IF NOT EXISTS task (
    id INT UNSIGNED NOT NULL,
    created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
    updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
    title VARCHAR(255) NOT NULL,
    PRIMARY KEY(id)
);

-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE task;

```

### modelの作成

それぞれのファイルに以下のように記述していきます。

```go

// src/model/model.go

package model

import (
    "database/sql"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

func DBConnect() (db *sql.DB) {
    dbDriver := "mysql"
    dbUser := "go_user"
    dbPass := "wataru"
    dbName := "go_web_app"
    dbOption := "?parseTime=true"
    db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName+dbOption)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

```

```go
// src/model/task.go

package model

import (
    "time"
)

type Task struct {
    ID uint `json:"id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
    Title string `json:"title"`
}
```

### controllerの作成

コントローラーについても同様です。

```go
// src/controller/task.go

package controller

import (
    "fmt"
    "net/http"
    "time"

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

func TasksGET(c *gin.Context) {
    db := model.DBConnect()
    result, err := db.Query("SELECT * FROM task ORDER BY id DESC")
    if err != nil {
        panic(err.Error())
    }

    tasks := []model.Task{}

    for result.Next() {
        task := model.Task{}
        var id uint
        var createdAt, updatedAt time.Time
        var title string

        err = result.Scan(&id, &createdAt, &updatedAt, &title)
        if err != nil {
            panic(err.Error())
        }

        task.ID = id
        task.CreatedAt = createdAt
        task.UpdatedAt = updatedAt
        task.Title = title
        tasks = append(tasks, task)
    }
    fmt.Println(tasks)
    c.JSON(http.StatusOK, gin.H{"tasks": tasks})
}

```

今回は読み取り(Read)のみ実装しています。

### main.go

```go
// main.go

package main

import (
    "./src/controller"
    "github.com/gin-gonic/gin"
)

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

    // API namespace
    v1 := router.Group("/api/v1")
    {
        v1.GET("/tasks", controller.TasksGET)
    }

    router.Run(":8080")
}

```

これでサーバーを立てることによってlocalhost:8080/api/v1/tasksを叩くことでデータベースに格納されたデータ(タスク一覧)を読み取ることができます。

# 確認

まずはデータベースにデータを格納します。

```sql
mysql> use task;
mysql> insert into task (id, title) values
           (1, "todo1"),
           (2, "todo2"),
           (3, "todo3");

# データの確認
mysql> select * from task;
+----+----------------------------+----------------------------+-------+
| id | created_at                 | updated_at                 | title |
+----+----------------------------+----------------------------+-------+
|  1 | 2019-12-26 18:28:57.564010 | 2019-12-26 18:28:57.564010 | todo1 |
|  2 | 2019-12-26 18:29:26.372062 | 2019-12-26 18:29:26.372062 | todo2 |
|  3 | 2019-12-26 18:29:36.072964 | 2019-12-26 18:29:36.072964 | todo3 |
+----+----------------------------+----------------------------+-------+
```

データが格納されていることが確認できたのでローカルサーバーを建てます。

```bash
go_webapi $ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /api/v1/tasks             --> _/Users/wataru_tajima/projects/00_self/go_webapi/src/controller.TasksGET (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
```

コマンドラインからcurlコマンドでデータを読み取ります。

```bash
$ curl localhost:8080/api/v1/tasks | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   343  100   343    0     0  46960      0 --:--:-- --:--:-- --:--:-- 49000
{
  "tasks": [
    {
      "id": 3,
      "created_at": "2019-12-26T18:29:36.072964Z",
      "updated_at": "2019-12-26T18:29:36.072964Z",
      "title": "todo3"
    },
    {
      "id": 2,
      "created_at": "2019-12-26T18:29:26.372062Z",
      "updated_at": "2019-12-26T18:29:26.372062Z",
      "title": "todo2"
    },
    {
      "id": 1,
      "created_at": "2019-12-26T18:28:57.56401Z",
      "updated_at": "2019-12-26T18:28:57.56401Z",
      "title": "todo1"
    }
  ]
}
```

正しく読み取ることができました〜

# 今後の展望
読み取りのみではなく追加、編集、削除、絞りこみ等の処理を実装する
ローカルじゃなくてサーバーにあげてみる

最後までご覧いただきありがとうございます！
また次回の記事でお会いしましょ〜🙌