golangでミニマムなREST APIを作る

# この記事は何？
注) golang初学者の私が初学者のために書いています。

https://tutorialedge.net/golang/creating-restful-api-with-golang/ のチュートリアル(英語だったが平易な文章で書かれてる。本当に)の内容をベースにして、challengeにあるupdate機能の追加の問題とMySQLを導入してみたという掛け算的内容です。
なので、上記チュートリアルが終了した段階からスタートするため悪しからず。

チュートリアル中のコードで参考になったこと。
https://qiita.com/ngplus6655/items/a38660313383d3ff2136

# 環境

ubuntu18.04LTS
go1.12.17 linux/amd64

github.com/gorilla/mux
github.com/jinzhu/gorm


## updateArticleを追加
まずはチュートリアル中のchallengeの内容を説いてみた

```golang

func updateArticle(w http.ResponseWriter, r *http.Request){
	vars := mux.Vars(r)
	id := vars["id"]
	reqBody, _ := ioutil.ReadAll(r.Body)
	var updateArticle Article
	json.Unmarshal(reqBody, &updateArticle)
	for index, article := range Articles {
		if article.Id == id {
			updateArticle.Id = id
			Articles[index] = updateArticle
		}
	}
}
```

returnSingleArticleと同様urlのidパラメータから編集するarticleを特定し、直接Articlesスライスの要素に代入しています。
直前にupdateArticle.Id = idとしているのは、パラメータidと送られたjsonデータのidが不一致だとArticleに対してIDがユニークではなくなるため。

## マルチプレクサにupdateArticleを登録

```
myRouter.HandleFunc("/article/{id}", updateArticle).Methods("PUT")
```

## curlしてみる

```
curl -X PUT  -H "Content-Type: application/json" -d '{"id": "1", "Title": "Updated Post", "desc": "the description for my updated post", "content": "my articles content"}' http://localhost:10000/article/1

curl -X GET http://localhost:10000/all
[{"Id":"1","Title":"Updated Post","desc":"the description for my updated post","content":"my articles content"},
{"Id":"2","Title":"Hello 2","desc":"Article Description","content":"Article Content"},
{"Id":"3","Title":"Hello 3","desc":"Article Description","content":"Article Content"}]
```

意図したとおりにidが１の要素を更新できました。


# mysqlの導入
## 完成形
```golang:main.go
package main

import (
    "fmt"
    "log"
	"net/http"
	"encoding/json"
	"io/ioutil"
	"strconv"

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

type Article struct {
	gorm.Model
	Title string `json:"Title"`
	Desc string `json:"desc"`
	Content string `json:"content"`
}

type Articles []Article

func connectDB() (*gorm.DB, error) {
	db, err := gorm.Open("mysql", "ユーザ名:パスワード@/test?charset=utf8&parseTime=True&loc=Local")
	return db, err
}

func initDb() *gorm.DB {
	db, err := connectDB()
	if err != nil {
		log.Fatalln("データベースの接続に失敗しました。")
	}
	return db
}

func idParamToUint(r *http.Request) uint {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	var uid uint = uint(id)
	return uid
}

func ParseJsonArticle(w http.ResponseWriter ,r *http.Request) Article {
	reqBody, _ := ioutil.ReadAll(r.Body)
	var article Article
	json.Unmarshal(reqBody, &article)
	return article
}

func homePage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Welcome to the HomePage!")
    fmt.Println("Endpoint Hit: homePage")
}

func returnAllArticles(w http.ResponseWriter, r *http.Request){
	fmt.Println("Endpoint Hit: returnAllArticles")
	db := initDb()
	var articles Articles
	db.Find(&articles)
	json.NewEncoder(w).Encode(articles)
}

func returnSingleArticle(w http.ResponseWriter, r *http.Request){
	fmt.Println("called returnSingleArticle")
	uid := idParamToUint(r)
	db := initDb()
	var article Article
	db.Where("id = ?", uid).First(&article)
	json.NewEncoder(w).Encode(article)
}

func createNewArticle(w http.ResponseWriter, r *http.Request) {
	fmt.Println("called createNewArticle")
	db := initDb()
	article := ParseJsonArticle(w, r)
	db.Create(&article)
	if db.NewRecord(article) {
		log.Println("新規articleの保存に失敗しました。")
	}
}

func updateArticle(w http.ResponseWriter, r *http.Request){
	fmt.Println("called updateAtricle")
	uid := idParamToUint(r)
	db := initDb()

	updatedArticle := ParseJsonArticle(w, r)

	var article Article
	db.Where("id = ?", uid).First(&article)

	article.Title = updatedArticle.Title
	article.Desc = updatedArticle.Desc
	article.Content = updatedArticle.Content
	db.Save(&article)
}

func deleteArticle(w http.ResponseWriter, r *http.Request) {
	fmt.Println("called deleteAtricle")
	uid := idParamToUint(r)
	db := initDb()
	db.Delete(Article{}, "id = ?", uid)
}

func handleRequests() {
	myRouter := mux.NewRouter().StrictSlash(true)
	myRouter.HandleFunc("/", homePage)
	myRouter.HandleFunc("/all", returnAllArticles)
	myRouter.HandleFunc("/article/{id}", returnSingleArticle).Methods("GET")
	myRouter.HandleFunc("/article", createNewArticle).Methods("POST")
	myRouter.HandleFunc("/article/{id}", updateArticle).Methods("PUT")
	myRouter.HandleFunc("/article/{id}", deleteArticle).Methods("DELETE")
	log.Fatal(http.ListenAndServe(":10000", myRouter))
}


func main() {
	fmt.Println("Rest API v2.0 - Mux Routers")
	db, err := connectDB()
	if err != nil {
		log.Fatalln("データベースの接続に失敗しました。")
	}
	defer db.Close()
	db.AutoMigrate(&Article{})
	
	handleRequests()
}
```

### MySQLのインストール
```
$ sudo apt install mysql-server mysql-client
$ mysql --version
mysql  Ver 14.14 Distrib 5.7.30, for Linux (x86_64) using  EditLine wrapper
```

### 起動
```
$ sudo service mysql start
```


### 初期設定->データベース作成->ユーザ追加
参考　https://qiita.com/houtarou/items/a44ce783d09201fc28f5

```
$ sudo mysql_secure_installation
$ sudo mysql -u root -p

mysql> CREATE DATABASE test;
mysql> set global validate_password_policy=LOW;
mysql> CREATE USER '名前'@'localhost' IDENTIFIED BY 'パスワード';
mysql> grant all on test.* to ユーザ名@localhost;
```

## Article構造体の再定義

```golang
type Article struct {
    gorm.Model
    Title string `json:"Title"`
    Desc string `json:"desc"`
    Content string `json:"content"`
}
type Articles []Article

```
IDやらタイムスタンプをGORMのモデル構造体に切り替えます。
スライスのArticlesはもう使いませんが一括で扱うところが出てくるので型として定義します。

## データベース接続

```golang
func connectDB() (*gorm.DB, error) {
	db, err := gorm.Open("mysql", "ユーザ名:パスワード@/テーブル名?charset=utf8&parseTime=True&loc=Local")
	return db, err
}

func initDb() *gorm.DB {
	db, err := connectDB()
	if err != nil {
		log.Fatalln("データベースの接続に失敗しました。")
	}
	return db
}
```


## /all
```golang
func returnAllArticles(w http.ResponseWriter, r *http.Request){
	db := initDb()
	var articles Articles
	db.Find(&articles)
	json.NewEncoder(w).Encode(articles)
}
```
先に定義した構造体をインスタンス化し、db.find関数ですべての行のデータを格納します。

##  /article/{id}  GET 
```golang
func idParamToUint(r *http.Request) uint {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	var uid uint = uint(id)
	return uid
}

func returnSingleArticle(w http.ResponseWriter, r *http.Request){
	uid := idParamToUint(r)
	db := initDb()
	var article Article
	db.Where("id = ?", uid).First(&article)
	json.NewEncoder(w).Encode(article)
}
```
urlパラメータのidはstring型ですが、MySQLに登録されているidはuint型です。そのためstring型をuint型に型変換する関数の定義をしています。
strconvパッケージのstringToUintは、uint64型がreturnされてしまうため使わず、いったんint型にしています。
それから、GORMのdb.Where関数つかってidが一致するarticleを一つだけ取り出します。

## /article POST
```golang
func ParseJsonArticle(w http.ResponseWriter ,r *http.Request) Article {
	reqBody, _ := ioutil.ReadAll(r.Body)
	var article Article
	json.Unmarshal(reqBody, &article)
	json.NewEncoder(w).Encode(article)
	return article
}

func createNewArticle(w http.ResponseWriter, r *http.Request) {
	fmt.Println("called createNewArticle")
	db := initDb()
	article := ParseJsonArticle(w, r)
	db.Create(&article)
	if db.NewRecord(article) {
		log.Println("新規articleの保存に失敗しました。")
	}
}
```
まずは、関数として(updateでも再利用するため)リクエストボディで送られてくるJSONをパースし、構造体として扱えるようにします。
GORMのCreate関数でMySqlにあたらしい行を追加します。

※ 編集しました(2020/7/16)
log.Fatalln -> log.Printlnに変更しました。
データの保存に失敗したときにプログラムを終了させる必要はないと考えるからです。

## /article/{id} PUT

```golang

func updateArticle(w http.ResponseWriter, r *http.Request){
	uid := idParamToUint(r)
	db := initDb()

	updatedArticle := ParseJsonArticle(w, r)

	var article Article
	db.Where("id = ?", uid).First(&article)

	article.Title = updatedArticle.Title
	article.Desc = updatedArticle.Desc
	article.Content = updatedArticle.Content
	db.Save(&article)
}
```
CRUDで最も複雑なupdateですが、今までの組み合わせで実現できました。ここでは、Article構造体を二つインスタンス化させています。
一つはMySQLから引っ張ってきたデータ用、もう一つはリクエストボディから更新後のデータとして入ってくるupdatedArticleで、一つ目のArticleに二つ目のupdatedArticleを代入後Save関数を呼び出しています。

## /article/{id} DELETE
```golang
func deleteArticle(w http.ResponseWriter, r *http.Request) {
	uid := idParamToUint(r)
	db := initDb()
	db.Delete(Article{}, "id = ?", uid)
}
```
GORMのDelete関数によって簡単に実装できました:relaxed:

## handleRequests関数
```golang

func handleRequests() {
	myRouter := mux.NewRouter().StrictSlash(true)
	myRouter.HandleFunc("/", homePage)
	myRouter.HandleFunc("/all", returnAllArticles)
	myRouter.HandleFunc("/article/{id}", returnSingleArticle).Methods("GET")
	myRouter.HandleFunc("/article", createNewArticle).Methods("POST")
	myRouter.HandleFunc("/article/{id}", updateArticle).Methods("PUT")
	myRouter.HandleFunc("/article/{id}", deleteArticle).Methods("DELETE")
	log.Fatal(http.ListenAndServe(":10000", myRouter))
}
```
ポイントは、"article/{id}" の.Methods("GET")省略していないところです。
仮に省略してしまうとPUTやDELETEをつけてcurlしても、すべてGETメソッドのreturnSingleArticle関数にルーティングされてしまいます。

## まとめ

GORMとGorilla/muxパッケージでいい感じにRestAPIを作れた。
特にGORMに関しては、公式ページが日本語化されていて楽だった。
急いでクライアント側も作らなくては！