【Go,PostgreSQL】を用いたアプリ開発の環境構築

# はじめに
個人開発をしている大学生やまいです。現在は読書家向けSNSを開発しており、本記事もそれの一環です。今後も本開発で学んだことを発信していくため、似たようなものを作りたいという方は追ってください。次回は本の感想を投稿・取得ｓAPI設計です。
本記事ではGoやDB,Dockerを用いた環境構築方法を紹介します。本記事さえ読めば環境構築ができるように細かく説明します。記事が長いため、必要な部分を目次から選んで読んでもらうのも良いと思います。
間違っていることがあればコメントで指摘していただけると幸いです。

:::note warn
本記事の環境構築方法はあくまで一例です。記述しているのは2025/03/04ということに留意してください。
:::

# 想定読者
・GoやDB,Dockerを用いてアプリ開発したいけど何から始めればいいか分からない方
・GoのフレームワークやDockerについて簡単に知りたい方

# 環境
<dl>
  <dt>OS</dt>
  <dd>Windows11</dd>
  <dt>Go</dt>
  <dd>1.23.2 windows/amd64</dd>
  <dt>GORM</dt>
  <dd>1.25.12</dd>
  <dt>Gin</dt>
  <dd>1.10.0</dd>
  <dt>PostgreSQL</dt>
  <dd>1.5.11</dd>
  <dt>Docker</dt>
  <dd>27.5.1</dd>
</dl>

# 構成
今回触るのは`backend`のみです。
```
root/
　├ backend/
　│　└ database/
　│　　　└ database.go
　│ └ models/
　│　　　└ review.go
　├ docker-compose.yml
　├ go.mod
　├ go.sum
　├ main.go
　├ docker/
　└ frontend/
```

# 使用技術の説明
## Gin
`Gin`はGo言語で使用するWebフレームワークです。Webサーバの構築や起動を簡単に行うことができます。

## GORM
`GORM`はGo言語の`ORM(Object-Relational Mapping)`ライブラリです。SQLを扱う必要がなく、GoアプリからDBを操作することができます。

## Docker
アプリケーションをコンテナという仮想環境で動かす技術です。`Docker`のコンテナ内にPostgreSQLをインストールすることでバージョン管理を簡単にしています。Goアプリから`Docker`のPostgreSQLに接続してデータを読み書きするという仕組みです。

# 環境構築
## 1.インストール
１\. Go言語のインストールとプロジェクトの初期化

https://qiita.com/ryome/items/8bc5d7c8cd51a351d016

`backend`で以下のコマンドを実行し、プロジェクトの初期化を行いましょう。

```sh
go mod init project-name
```

:::note
[ちゃんと理解するGo言語開発環境構築：go mod initとその必要性](https://qiita.com/TakanoriVega/items/6d7210147c289b45298a#go-mod-init%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B)によると

>`go mod init` は、新しいGoモジュールを初期化するためのコマンドです。このコマンドを実行すると、モジュール名をパラメータとして受け取り、その名前で新しいモジュールを作成します。さらに、このコマンドは`go.mod`という特別なファイルを生成します。このファイルには、モジュール名とモジュールが依存する他のモジュールの情報が含まれます。
:::

2\. Gin,GORM,PostgreSQLのインストール
```sh
go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
```

3\. 簡単なサーバ作成
ここで一度サーバを作って正常に動くか確認してみましょう。

```go:backend/main.go
package main

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

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

    r.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "Hello, Book Review App!"})
    })

    r.Run(":8080")
}
```
<details><summary>コード中身の説明</summary>
`GET`リクエストなので、サーバに「このstatusとmessageを返してね」とお願いしています。

`/`でルーティングを行っています。`func()`関数はHTTPリクエストを処理する関数であり、`c.JSON()`で返す中身を決めています。
・`http.statusOK`はリクエストが成功したかどうかを返します。
・`gin.H`では`{"キー" : 値}の形式でJSONデータを作ることができます。

:::note
webサーバは通常クライアントからのリクエストを受け取ってHTTPレスポンスを返します。
HTTPレスポンスの中身は「ステータスコード」「ヘッダー」「ボディ」です。
:::

</details>

`backend`で以下のコマンドを実行し、サーバを起動します。

```sh
go run main.go
```

ブラウザで`http://localhost:8080`にアクセスして以下のJSONが表示されたら成功です。

```json
{"message": "Hello, Book Review App!"}
```

## 2.PostgreSQLのセットアップ
今回はDockerを使用してPostgreSQLを起動します。
1\. Docker Desktopのインストール
以下の記事を参照してください。これをインストールすることでdockerコマンドが使えるようになります。

https://qiita.com/zembutsu/items/a98f6f25ef47c04893b3

2\. PostgreSQLを起動
```yml:backend/docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:latest
    container_name: book_review_db
    restart: always
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: book_review
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:
```

`backend`で以下のコマンドを実行しましょう。
本コマンドは`docker-compose.yml`を基にコンテナを起動するものであり、`-d`オプションはバックグラウンドで実行するというものです。

```sh
docker compose up -d
```

:::note
`docker-compose.yml`や上記のコマンドがよく分からない場合は以下の記事の`Dockerの機能と用語説明`を読んでみてください。5分で理解できます。

["Dockerって何？　簡単な言葉で解説するDockerの基本"](https://qiita.com/riku_ogawa/items/4ffc6aaf7a3df053a4ad)
:::

3\. GORMを使ってDBと接続
使用技術の説明の通りGORMはDBと接続するフレームワークです。そのための設定をしています。
```go:backend/database/database.go
package database

import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "log"
)

var DB *gorm.DB

func ConnectDB() {
    dsn := "host=localhost user=user password=password dbname=book_review port=5432 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    DB = db
    log.Println("Database connected!")
}
```

<details><summary>connectDB()関数の説明</summary>

`DSN(Data Source Name)`を作成
`host`:ローカル環境のDBに接続
`user`：DBのユーザー名
`password`:DBのパスワード
`dbname`：DB名
`port`：PostgreSQLのポート
`sslmode=dosable`:SSLを使わずに接続（ローカル）

`gorm.Open()`で接続し、失敗した場合は`err`が発生する。失敗した場合はDBに接続情報を保存してログ出力している。

</details>

4\. モデルを作成
DBの中身として、どういうものを保持するのか決定します。`primaryKey`は主キーを示しています。
```go:backend/models/review.go
package models

import "time"

type Review struct {
    ID        uint      `gorm:"primaryKey"`
    BookTitle string    `gorm:"not null"`
    Content   string    `gorm:"not null"`
    CreatedAt time.Time `gorm:"autoCreateTime"`
}
```

5\. マイグレーションを実行
マイグレーションとはDBの構造を管理する仕組みのことです。これをコードベースで管理します。
```go:backend/main.go
package main

import (
    "book-review-app/database"
    "book-review-app/models"
    "github.com/gin-gonic/gin"
    "log"
)

func main() {
    // データベース接続
    database.ConnectDB()

    // マイグレーション
    err := database.DB.AutoMigrate(&models.Review{})
    if err != nil {
        log.Fatal("Migration failed:", err)
    }

    r := gin.Default()

    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "Hello, Book Review App!"})
    })

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

<details><summary>マイグレーション部分の説明</summary>

GORMの`AutoMigrate()`を使ってDBのテーブルを自動で作成・更新する処理を行っています。先ほど作成した`models.Review`に基づいてreviewsテーブルを作成しています。

</details>


以下のコマンドを実行してマイグレーションを実行しましょう。
```sh
go run main.go
```

# 参考文献

https://qiita.com/ryome/items/8bc5d7c8cd51a351d016

https://qiita.com/TakanoriVega/items/6d7210147c289b45298a#go-mod-init%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B

https://qiita.com/zembutsu/items/a98f6f25ef47c04893b3

https://qiita.com/riku_ogawa/items/4ffc6aaf7a3df053a4ad