Goのginとgormで簡単なWebアプリケーションを作る。

# はじめに
どもです。
最近、Webアプリケーションを久しぶりに作るってなって、GoでのCRUDの書き方等を忘れたので、メモといった感じで記していきます。

ルーティングとCRUDについて詳しく書いていきます。

githubに今回作成した**[ソースコード](https://github.com/marnysan111/Web-jikken4)**を載せておきます

# 使用する環境

## 実際の環境

|項目|使用したもの|
|---|---|
|OS|CentOS8|
|DB|MySQL|
|バックエンド|Go|

## Goで使用したパッケージ

|用途|パッケージ名|
|---|---|
|ルーティング|gin|
|ORM|gorm|

開発環境に関しては、すでに終わっていることを仮定します。


# 実装する内容

今回作成するものは、元から存在するアプリケーションのユーザの管理をするアプリケーションです。
本来、gormのマイグレーションというのもできますが、今回はすでにテーブル等が作成されているものとします。

1. ユーザの一覧（Select）
2. ユーザの詳細（Select）
3. ユーザの削除（Delete）
4. ユーザの編集（Update）

といったものを実装していきます。


# ディレクトリ構造

```
.
|- handler.go   //DBにCRUDする関数を書く
|- main.go      //GETとPOSTの処理を書く
|- routing.go   //ルーティングの関数を書く
|- views/
    |- css/
    |      |- main.css
    |- html/
    |      |- delete.html
    |      |- detail.html
    |      |- index.html
    |      |- update.html
    |- js/
           |- main.js

```

# gin

ginのGoDocは**[こちら](https://godoc.org/github.com/gin-gonic/gin)**から

ginというのは、GoのWebフレームワークで、とりあえず早い。ライバルとしてはechoがあります。
自分的には、どっちでもいいのかなと思います。ただ、ginのほうが簡潔に書けるという印象があります


## 実装

```go:main.go
package main

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

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("./views/html/*.html")
	r.Static("/css", "./views/css")

	r.GET("/", index)
	r.GET("/detail/:id", detail)
	r.GET("/delete/:id", delete)
	r.GET("/update/:id", update)

	r.POST("/do_delete/:id", do_delete)
	r.POST("/do_update/:id", do_update)

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

```go:routing.go
package main

import (
	"fmt"
	"net/http"
	"strconv"

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

func index(c *gin.Context) {
	users, err := UserGetAll()
	if err != nil {
		fmt.Println(err)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	c.HTML(http.StatusOK, "index.html", gin.H{"users": users})
}

～～略～～

func do_update(c *gin.Context) {
	n := c.Param("id")
	name := c.PostForm("name")
	email := c.PostForm("email")
	id, err := strconv.Atoi(n)
	if err != nil {
		fmt.Println(err)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	err = UserUpdate(id, name, email)
	if err != nil {
		fmt.Println(err)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	c.Redirect(302, "/")
}
```


## 解説
主に使っている関数をピックアップして、紹介します。


### main.go
まずはここ。main関数の

```go
r := gin.Default()
```
こいつで、`r` ってのがginパッケージ使いまっせーの宣言をする。`r`変数は好きにつけてぐださい

```go
r.LoadHTMLGlob("./views/html/*.html")
r.Static("/css", "./views/css")
r.GET("/", index)
r.POST("/do_delete/:id", do_delete)
r.Run(":80")
```

`r`を用いて、このような関数を呼び出してます。

`LoadHTMLGlob`：htmlファイルの場所の指定
`Static`：css等の静的ファイルの指定（jsでも同様）
`GET`：GETの処理（第一引数がドメイン以降のパス指定、第二引数がそのパスに対応した関数の呼び出し）
`POST`：POSTの処理（第一引数がドメイン以降のパス指定、第二引数がそのパスに対応した関数の呼び出し）
`Run`：サーバの起動（:80は80番ポートで起動してます）

GETとPOSTで呼び出す関数は、次の`routing.go`に書いてます。

### routing.go

`GET`と`POST`という処理があって、とりあえず`GET`だったらHTMLを出力して`POST`だったらリダイレクトする的なイメージがあると思います。

とりあえず、各関数の引数に`c *gin.Context`とありますが、先ほどの`r`みたいな感じです。

#### 関数の解説

```go
c.AbortWithStatus(http.StatusInternalServerError)
c.HTML(http.StatusOK, "index.html", gin.H{"users": users})
c.Redirect(302, "/")
```

`c`を用いて、このような関数を呼び出してます。

`AbortWithStatus(http.StatusInternalServerError)`：リクエストの認証に失敗したら、401を返す
`HTML`：htmlファイルを返す。（第一引数は置いておいて、第二引数はhtmlファイルの指定、第三引数はhtmlに値を渡す変数の定義）
`Redirect`：リダイレクトをする。（第一引数で302で返す宣言、第二引数でリダイレクト先の指定）

HTMLに変数を渡す場合の処理は、`Go Template`とかで検索すると、いろいろ出てくるので、そちらをどうぞ。

`main.go`と`routing.go`は以上。これで、ページ遷移くらいはできると思います。単にHTMLを出力するサンプルコードを載せて、次に行きます。

```go:サンプルコード
package main

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

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("./views/html/*.html")

	r.GET("/", index)

	r.Run(":80")
}

func index(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", gin.H{})
}
```
これで、`index.html`は、出力するはず。（エラーだったらコメントください）


# gorm

gormの公式ドキュメントは**[こちら](https://gorm.io/ja_JP/docs/index.html)**から

gormというのは、Go用のORMライブラリで、DB操作を簡単にできるすげーいいやつ。
公式ドキュメントが超優秀なので、ぜひ見てみてください

## 実装

```go:handler.go
package main

import (
	"fmt"

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

type Users struct {
	Id       int
	Name     string
	Email    string
	Password string
}

type Posts struct {
	Id       int
	Author   int
	Title    string
	Content  string
	Comments int
}

const connect = "root:root@tcp(127.0.0.1:3306)/laravel?charset=utf8mb4&parseTime=True&loc=Local"

func UserGetAll() ([]Users, error) {
	db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		return nil, fmt.Errorf("UserGetAll失敗: %w", err)
	}
	var users []Users
	err = db.Select("id", "name", "email", "password").Find(&users).Error
	if err != nil {
		return nil, fmt.Errorf("UserGetAll_Select失敗: %w", err)
	}
	return users, nil
}

func UserGetOne(id int) (Users, error) {
	db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	var user Users
	if err != nil {
		return Users{}, fmt.Errorf("UserGetOne失敗: %w", err)
	}
	err = db.First(&user, id).Error
	if err != nil {
		return Users{}, fmt.Errorf("UserGetOne_First失敗: %w", err)
	}
	return user, nil
}

func PostGetAll(author int) ([]Posts, error) {
	db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		return nil, fmt.Errorf("PostGetAll失敗: %w", err)
	}
	var posts []Posts
	err = db.Where("author = ?", author).Find(&posts).Error
	if err != nil {
		return nil, fmt.Errorf("PostGetAll_Select失敗: %w", err)
	}
	return posts, nil
}

func UserDelete(id int) error {
	db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		return fmt.Errorf("UserDelete失敗: %w", err)
	}
	var users Users
	err = db.Delete(&users, id).Error
	if err != nil {
		return fmt.Errorf("UserDelete_Delete失敗: %w", err)
	}
	return nil
}

func UserUpdate(id int, name string, email string) error {
	db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		return fmt.Errorf("UserUpdate失敗: %w", err)
	}
	var users Users
	err = db.First(&users, id).Error
	if err != nil {
		return fmt.Errorf("UserUpdate_First失敗: %w", err)
	}
	users.Name = name
	users.Email = email
	db.Save(&users)
	return nil
}
```

## 解説

### handler.go

各関数の最初に書かれている、`gorm.Open`が最重要で、DB（MySQL）に接続する関数で、`count`で定義したDB情報を呼び出して使ってます。

自分の関数の命名についてですが、`〇〇GetAll`だと要素の全取得、`〇〇GetOne`だと一つだけ取得、`〇〇Delete`だと削除、`〇〇Update`だと更新、みたいな感じです


流れとしては、`Users`や`Posts`のように、必要な情報を構造体として扱って処理をします。
ここでは、gormの関数が多いので、よく使うものだけピックアップします。

#### 関数の解説
`First`：なにか指定したものの最初にマッチした要素を取得する
`Where`：データの特定をするために、カラムを指定する
`Find`：Firstと異なり、特定の一つのデータを取得する

と、公式ドキュメントにもありますが、関数とSQL文がいい具合に処理できるので、すごく便利。
自分の場合、`err`としてエラーハンドリングをしつつ処理をしています。
（確かGoのすごい人からおしえてもらった書き方だった気がする…）


# 終わり
かなり長くなってしまいましたが、これで解説は終わりです。

Goを用いたWebアプリケーション開発というのは、これからもっとはやっていくと思います。そこで、この記事が参考になることを祈ります。

疲れた…