【Go言語】Echo+GORM+MySQLでCRUDへの挑戦！！

#自己紹介
普段私は、
一番得意な機械学習(深層学習)をしたり、
Python/Django でWebアプリを開発したり、
TypeScript/Vue or React でフロントエンドの開発をしたり、
PHP/Laravel でWebアプリを開発したり、
さまざまなことを行っています。

趣味で休みの日にGo言語で色々作成しているのですが、型のある世界は素敵だなと昨今感じています。
今最もやりたいことは、Goで大規模なWebアプリケーションを作成したい。

企業案件やご連絡等ございましたらお気軽に下記よりご連絡いただければと思います。
kohei0801nagamatsu@gmail.com

# 今回やりたいこと
Go言語でもORMマッパーを使用したいということで、
GORMと呼ばれるパッケージを使用するまでについて記事を書こうと思います。
バックエンドをGo言語のEcho
フロントエンドをVue.js(composition api)
で分けているため、今回の記事はバックエンドまでの説明になりますので、
最終的なCRUDの確認は`curl`or`post man`で確認してみてください！！

#環境
MacOS
go v1.16.4
Echo v3.3.10+incompatible
GORM v1.21.11

Goのプロジェクトは全てGo Modulesで管理しています。

#手順
Echoはインストール済みとします。
インストール方法は以下の記事がわかりやすいです。
[参考記事](https://selegee.com/7167/)


#ディレクトリ構造
```
.
├── LICENSE
├── README.md
├── common
│   ├── ats.go
│   └── spreadsheet.go
├── config
│   └── config.go
├── config.ini
├── go.mod
├── go.sum
├── handler
│   ├── auth.go
│   ├── create.go
│   └── handler.go
├── main.go
├── model
│   ├── db.go
│   ├── todo.go
│   └── user.go
├── public
│   ├── service-account.json
│   └── test.txt
├── router.go
└── service
    └── file.go
```
GORMのインストール
GORMはGo言語のObject-relational mappingです。

```
$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql
```

# configの設定
DBの情報をconfig.iniファイルに記載して読み込みます。
configファイルの読み込みはini.v1を使用する、今回SpreadSheetの操作もあるためSpreadSheetIdも記載しています。

設定方法は以下の記事を参考にしてください
[[Go言語]Configファイルをini.v1で読み込む方法](https://selegee.com/7234/)


```./config.ini
[db]
host = 127.0.0.1
port = 8889
user = root
password = root
name = example

[spreadsheet]
spreadsheetID = XXXXXXXXXXXXXXXXX
```

```./config/config.go
package config

import (
	"log"
	"os"

	"gopkg.in/ini.v1"
)

type ConfigList struct {
	DbHost     string
	DbPort     string
	DbUser     string
	DbPassword string
	DbName     string
	SpreadID   string
}

var Config ConfigList

func init() {
	cfg, err := ini.Load("config.ini") // configファイル読み込み
	if err != nil {
		log.Printf("Failed to read file: %v", err)
		os.Exit(1) // プログラム終了
	}

	Config = ConfigList{
		DbHost:     cfg.Section("db").Key("host").String(),
		DbPort:     cfg.Section("db").Key("port").String(),
		DbUser:     cfg.Section("db").Key("user").String(),
		DbPassword: cfg.Section("db").Key("password").String(),
		DbName:     cfg.Section("db").Key("name").String(),
		SpreadID:   cfg.Section("spreadsheet").Key("spreadsheetID").String(),
	}
}

```
#MySQLへの接続
MySQLと接続します。
今回MySQLをbrewコマンドでinstallしたものを使用しています。
環境に合わせて接続先を変えてください。

```db.go
package model

import (
	"make-ats-code/config"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var db *gorm.DB
var err error

func init() {
	// connect DB
	conf := config.Config
	dsn := conf.DbUser + ":" + conf.DbPassword + "@tcp(" + conf.DbHost + ":" + conf.DbPort + ")/" + conf.DbName + "?charset=utf8mb4&parseTime=True&loc=Local"
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	db.AutoMigrate(&User{})
}
```

#Model の作成
```user.go
package model

type User struct {
	ID       int    `json:"id" gorm:"praimaly_key"`
	Name     string `json:"name"`
	Password string `json:"password"`
}

func CreateUser(user *User) {
	db.Create(user)
}

func FindUser(u *User) User {
	var user User
	db.Where(u).First(&user)
	return user
}
```
jsonで返す場合のkeyを`json:"id"`のように記載しています。記載しないとそのまま大文字のIDが使われてしまいます。
今回User構造体をあらかじめ定義しており、その構造体を元にDBの操作を行なっております。
Go言語にはクラスの概念がなく、structとinterfaceでクラスの役割を果たせます。
CreateUser関数　新規ユーザーの作成
FindUser関数　ユーザーを検索しuserのポインタ（格納先にのアドレス）を返す

また、MySQLの型を`gorm:"praimaly_key"`のように設定しています。マイグレーション時に、指定した型でテーブルが作成されます。

#コントローラーを作成(handlerディレクトリ)
次にhandlerの作成をします。
`sign up` `log in`を行います。
ログイン時はその人が正しいかどうかを判定して、jwtトークンを持たせた構造体をreturnしています。

```auth.go
package handler

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

	"make-ats-code/model"

	"github.com/dgrijalva/jwt-go"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

type jwtCustomClaims struct {
	UID  int    `json:"uid"`
	Name string `json:"name"`
	jwt.StandardClaims
}

var signingKey = []byte("secret")

var Config = middleware.JWTConfig{
	Claims:     &jwtCustomClaims{},
	SigningKey: signingKey,
}

func Signup(c echo.Context) error {
	user := new(model.User)
	if err := c.Bind(user); err != nil {
		return err
	}

	if user.Name == "" || user.Password == "" {
		return &echo.HTTPError{
			Code:    http.StatusBadRequest,
			Message: "invalid name or password",
		}
	}

	if u := model.FindUser(&model.User{Name: user.Name}); u.ID != 0 {
		return &echo.HTTPError{
			Code:    http.StatusConflict,
			Message: "name already exists",
		}
	}

	model.CreateUser(user)
	user.Password = ""

	return c.JSON(http.StatusCreated, user)
}

func Login(c echo.Context) error {
	u := new(model.User)
	if err := c.Bind(u); err != nil {
		return err
	}

	user := model.FindUser(&model.User{Name: u.Name})
	if user.ID == 0 || user.Password != u.Password {
		return &echo.HTTPError{
			Code:    http.StatusUnauthorized,
			Message: "invalid name or password",
		}
	}

	claims := &jwtCustomClaims{
		user.ID,
		user.Name,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
		},
	}
	fmt.Println(claims.UID, claims.Name)

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	t, err := token.SignedString(signingKey)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, map[string]string{
		"token": t,
	})
}

```

＃ルーティングの設定

```router.go
package main

import (
	"make-ats-code/handler"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

func newRouter() *echo.Echo {
	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.Static("/assets", "public/assets")

	e.File("/", "public/index.html")
	e.POST("/signup", handler.Signup)
	e.POST("/login", handler.Login)

	return e
}

```

#実行
```
$ go run main.go router.go
```
この時にusersテーブルが存在しなければテーブルが作成されます。

#ユーザーの作成
curlコマンドでAPIを試します。

postで新しくレコードを追加します。

```
$ curl --location --request POST 'http://localhost:8080/signup' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "テスト",
    "password": "test password"
}'
```

#まとめ
Echo+GORMでMySQLと接続してCRUDを試すことができました。
みなさんもGoライフを楽しみましょう。