ToDoアプリの作成（マイグレーション編）

<h2>はじめに</h2>
Udemyの講座「Echo/Go + Reactで始めるモダンWebアプリケーション開発」を基にEcho/Goの部分だけにはなるのですが、勉強した内容をまとめていこうと思います。<br>
長くなるので何回かに分けてブログを執筆しようと思っています。

<h2>プロジェクトの作成</h2>
まずはVSCodeにてフォルダ(GO-REST-API)を作成し，下記コマンドを実行し新しくGoのプロジェクトを作成します。<br>

```go mod init go-rest-api```


<h2>ローカルでPostgreSQLを起動</h2>
ローカルでpostgresを起動するため、プロジェクト直下にdocker-compose.ymlファイルを作成します。

```docker:docker-compose.yml
version: "3.8"
services:
  dev-postgres:
    image: postgres:15.1-alpine
    ports:
      - 5434:5432
    environment:
      //PostgreSQLに必要なユーザー名
      POSTGRES_USER: udemy
      //PostgreSQLに必要なパスワード
      POSTGRES_PASSWORD: udemy
      //PostgreSQLに必要なDB名
      POSTGRES_DB: udemy
    restart: always
    networks:
      - lesson
networks:
  lesson:
```

上記のdocker-compose.ymlを作成したら、```docker compose up -d```でコンテナを立ち上げていきます。
※念の為```docker ps```でpostgres:15.1-alpineのコンテナが起動しているかを確認しておく。

<h2>環境変数の設定</h2>
下記のファイルを作成して、アプリケーションに使用する環境変数の設定を行います。

```.env
PORT=8080
//PostgreSQLに必要なユーザー名
POSTGRES_USER=udemy
//PostgreSQLに必要なパスワード
POSTGRES_PW=udemy
//PostgreSQLに必要なDB名
POSTGRES_DB=udemy
//PostgreSQLのポート番号
POSTGRES_PORT=5434
//PostgreSQLのホスト名
POSTGRES_HOST=localhost
//JWTを生成するためのシークレットキー（任意の値）
SECRET=uu5pveql
//開発ノード
GO_ENV=dev
API_DOMAIN=localhost
//フロントエンドのパス
FE_URL=http://localhost:3000
```

<h2>ユーザーのモデル構造作成</h2>
下記のコードは、GORMライブラリを使って、データベースにユーザー情報を保存し、APIレスポンスを生成する際に使用されます。<br>
下記のコードは、ユーザーを表すデータ構造 (User) と、APIレスポンスとして使用するデータ構造 (UserResponse) を定義しています。<br>User 構造体は、ユーザーのID、メールアドレス、パスワード、作成日時、更新日時を格納します。<br>UserResponse 構造体は、APIレスポンスとして、ユーザーのIDとメールアドレスをクライアントに返します。

```model/user.go
package model

import "time"

type User struct {
    //構造体のオブジェクトをJSONに変換したときに自動的に小文字に変更
	ID        uint      `json:id gorm:"primaryKey"`
	Email     string    `json:"email" gorm:"unique"`
	Password  string    `json:"password"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type UserResponse struct {
	ID    uint   `json:id gorm:"primaryKey"`
	Email string `json:"email" gorm:"unique"`
}
```

```json:```とすると、構造体のオブジェクトをJSONに変換したときに自動的にフィールド名を小文字に変更してくれる。
また、```gorm:"primaryKey```はオブジェクトの主キーとして認識する。
```gorm:"unique```は重複を許さないユニークなフィールドという制約をつけることが出来る。

6.タスクのモデル構造作成
下記のコードは、Go言語でREST APIアプリケーションで使用されるタスクのデータ構造（モデル）を定義しています。
下記のコードは、タスクを表すデータ構造 (Task) と、APIレスポンスとして使用するデータ構造 (TaskResponse) を定義しています。<br>Task 構造体は、タスクのID、タイトル、作成日時、更新日時、関連するユーザーの情報を格納します。<br>TaskResponse 構造体は、APIレスポンスとして、タスクのID、タイトル、作成日時、更新日時をクライアントに返します。

```
model/task.go
package model

import "time"

type Task struct {
	ID        uint      `json:"id" gorm:"primaryKey"`
	Title     string    `json:"title" gorm:"not null"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	User      User      `json: "user" gorm:"foreignKey:UserId: constraint:OnDelete:CASCADE"`
	UserId    uint      `json:"user_id" gorm:"not null"`
}

type TaskResponse struct {
	ID        uint      `json:id gorm: "primaryKey"`
	Title     string    `json: "title" gorm: "not null"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}
```

```gorm:"foreignKey:UserId:```はユーザーモデルの主キーであるIDの値がタスクモデルのUserIdに格納されます。
1対多のリレーションを張ることが出来る。（複数のテーブルと関連付けることが出来る）

```gorm: "not null"```空の値を許可しない。

```constraint:OnDelete:CASCADE```はユーザーを削除した際、削除したユーザーのタスクを自動的に削除します。

<h2>DBへの接続</h2>
下記のコードは、Go言語でデータベースへの接続を作成し、その接続を管理する関数を提供する db パッケージです。<br>
下記のコードは、NewDB() 関数を使って、PostgreSQLデータベースへの接続を作成します。<br>環境変数 GO_ENV が "dev" に設定されている場合は、.env ファイルから環境変数をロードします。<br>データベース接続が成功すると、"Connected" を出力して、データベース接続オブジェクトを返します。<br>CloseDB() 関数は、データベース接続オブジェクトを引数に受け取って、データベース接続を閉じます。

```db/db.go
package db

import (
	"fmt"
	"log"
	"os"

	"github.com/joho/godotenv"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

func NewDB() *gorm.DB {
    //環境変数 `GO_ENV` が "dev" に設定されている場合は、`godotenv.Load()` を使って `.env` ファイルから環境変数をロードします。
	if os.Getenv("GO_ENV") == "dev" {
		err := godotenv.Load()
		if err != nil {
			log.Fatalln(err)
		}
	}
    //環境変数からPostgreSQLの接続URLを作成します。
	url := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", os.Getenv("POSTGRES_USER"),
		os.Getenv("POSTGRES_PW"), os.Getenv("POSTGRES_HOST"),
		os.Getenv("POSTGRES_PORT"), os.Getenv("POSTGRES_DB"))
    //PostgreSQLドライバーを使って、データベースに接続します。
	db, err := gorm.Open(postgres.Open(url), &gorm.Config{})
    //エラーが発生した場合、ログにエラーメッセージを出力してプログラムを終了します。
	if err != nil {
		log.Fatalln(err)
	}
    //接続が成功した場合、"Connected" を出力します。
	fmt.Println("Connected")
    //データベース接続オブジェクトを返します。
	return db
}

func CloseDB(db *gorm.DB) {
    //GORMデータベース接続から、SQLデータベース接続オブジェクトを取得します。
	sqlDB, _ := db.DB()
    //SQLデータベース接続を閉じます。エラーが発生した場合、ログにエラーメッセージを出力してプログラムを終了します。
	if err := sqlDB.Close(); err != nil {
		log.Fatalln(err)
	}
}
```

※外部パッケージを使用するので```go mod tidy```コマンドを実行してインストールします。


<h2>マイグレーション</h2>
下記のコードは、REST APIアプリケーションの初期設定として、データベースへの接続を作成し、データベーススキーマを自動的にマイグレートします。<br>
ここでのポイントは、AutoMigrate 関数を使い、User 構造体と Task 構造体のスキーマをデータベースに自動的に作成します。

```migrate/migrate.go
package main

import (
	"fmt"
	"go-rest-api/db"
	"go-rest-api/model"
)

func main() {
    //`db` パッケージの `NewDB()` 関数を呼び出して、データベースへの接続を作成し、接続オブジェクト `dbConn` に格納します。
	dbConn := db.NewDB()
    //`defer` キーワードを使って、`main` 関数の最後（プログラムの終了前）に "Successfully Migrated" を出力する処理を登録します。
	defer fmt.Println("Successfully Migrated")
    //`defer` キーワードを使って、`main` 関数の最後（プログラムの終了前）に `db` パッケージの `CloseDB()` 関数を呼び出して、データベース接続を閉じる処理を登録します。
	defer db.CloseDB(dbConn)
    //`dbConn.AutoMigrate(&model.User{}, &model.Task{})`:  `dbConn` オブジェクトを使って、`User` 構造体と `Task` 構造体のスキーマをデータベースに自動的にマイグレートします。
	dbConn.AutoMigrate(&model.User{}, &model.Task{})
}
```

下記コマンドでマイグレーションを実行します。
```GO_ENV=dev go run migrate/migrate.go```

成功すると下記のメッセージが出てきます。
```
Connected
Successfully Migrated
```


<h2>データベースにテーブルが生成されているか確認</h2>
pgAdminにて確認していきます。
初めて起動する場合は、好きなパスワードを設定してください。
下記画像のAdd New Serverをクリックします。

![スクリーンショット 2024-10-18 22.16.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/aac52807-9abf-b80b-e6e5-242fe21250b1.png)


下記画像のNameを設定を行います。（好きな名前で大丈夫です）
![スクリーンショット 2024-10-17 17.42.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912209/325ad235-5626-3ff3-8939-43b13facc9bf.png)

Connectionタブに移動して.envで設定している環境変数を入力。
![スクリーンショット 2024-10-18 21.22.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912209/b121e579-4412-0ba5-3444-f1a1a39e8371.png)

tasksテーブルにtask.goで定義したフィールドが反映されているか確認。
![スクリーンショット 2024-10-18 21.24.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912209/7eb2b287-2b8d-a907-c303-8d4565def327.png)
tasksテーブルにtask.goで定義したフィールドが反映されています。
![スクリーンショット 2024-10-18 21.31.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912209/8cee1af8-94e8-054c-3798-5e9c1351a9d7.png)

今度はusersテーブルにuser.goで定義したフィールドが反映されているか確認。
![スクリーンショット 2024-10-18 21.33.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912209/019242b2-3771-de49-f567-84e7b4b88d3a.png)
usersテーブルにuser.goで定義したフィールドが反映されています。
![スクリーンショット 2024-10-18 21.34.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912209/cfc63498-e679-e2b1-606a-71c140d35628.png)

<h2>まとめ</h2>
今回はマイグレーションのやり方についてまとめました。<br>
次回はユーザーの新規追加、ログイン、ログアウト機能を実装していきます。