Skip to content

Goでデータベースにアクセスする#

データベースとは、アプリケーションの情報を保存・取得する場所です。 GoではGORM、sqlx、標準のdatabase/sqlパッケージなど、様々なライブラリを使ってデータベースにアクセスできます。

一般的にモデルとは、システム上の登場人物(登場するモノ)を指します。

Goでも、モデルは登場人物単位で作ると良いです。 例えば予約システムだと、モデルには「お客様」と、「予約」という登場人物があり、 「お客様」には、氏名、電話番号、アカウント名があり、 「予約」には、日時、注文内容がある、 ような感じです。

ここでは、「お客様」情報としてUserモデルを作ってみます。SQLiteデータベースを使い、GORMというORMライブラリを使用します。

前提#

もし、Goプロジェクトが手元になければ、こちらの手順でGoプロジェクトを用意してください。

GORMのセットアップ#

まず、GORMとSQLiteドライバをインストールします:

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

プロジェクト構造の設定#

より整理された構造でプロジェクトを進めるため、以下のようなディレクトリ構造を作成しましょう:

go-web-server/
  ├── main.go
  ├── models/
      └── user.go
  ├── database/
      └── database.go
  └── handlers/
       └── user_handler.go

必要なディレクトリを作成します:

mkdir -p models database handlers

モデルの定義#

models/user.goファイルを作成して、ユーザーモデルを定義します:

package models

import (
    "time"

    "gorm.io/gorm"
)

// User はユーザー情報を表す構造体です
type User struct {
    ID          uint           `json:"id" gorm:"primaryKey"`
    Name        string         `json:"name" gorm:"not null"`
    Tel         string         `json:"tel" gorm:"not null"`
    AccountName string         `json:"account_name" gorm:"not null;unique"`
    CreatedAt   time.Time      `json:"created_at"`
    UpdatedAt   time.Time      `json:"updated_at"`
    DeletedAt   gorm.DeletedAt `json:"-" gorm:"index"`
}

// NewUser はユーザー作成時に使用する構造体です
type NewUser struct {
    Name        string `json:"name" binding:"required"`
    Tel         string `json:"tel" binding:"required"`
    AccountName string `json:"account_name" binding:"required"`
}

データベース接続の設定#

database/database.goファイルを作成して、データベース接続を管理します:

package database

import (
    "log"

    "go-web-server/models"

    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

// DB はアプリケーション全体で使用するデータベース接続です
var DB *gorm.DB

// InitDatabase はデータベースを初期化します
func InitDatabase() {
    var err error

    // SQLiteデータベースに接続
    DB, err = gorm.Open(sqlite.Open("app.db"), &gorm.Config{})
    if err != nil {
        log.Fatal("データベースへの接続に失敗しました:", err)
    }

    // マイグレーションを実行(テーブルを自動作成)
    err = DB.AutoMigrate(&models.User{})
    if err != nil {
        log.Fatal("マイグレーションに失敗しました:", err)
    }

    log.Println("データベースの初期化が完了しました")
}

ユーザー操作のハンドラー#

handlers/user_handler.goファイルを作成して、ユーザーのCRUD処理を実装します:

package handlers

import (
    "net/http"
    "strconv"

    "go-web-server/database"
    "go-web-server/models"

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

// GetAllUsers は全ユーザーを取得するハンドラーです
func GetAllUsers(c *gin.Context) {
    var users []models.User

    result := database.DB.Find(&users)
    if result.Error != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "ユーザーの取得に失敗しました"})
        return
    }

    c.JSON(http.StatusOK, users)
}

// GetUser は特定のユーザーを取得するハンドラーです
func GetUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "無効なIDです"})
        return
    }

    var user models.User
    result := database.DB.First(&user, id)

    if result.Error != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "ユーザーが見つかりません"})
        return
    }

    c.JSON(http.StatusOK, user)
}

// CreateUser は新しいユーザーを作成するハンドラーです
func CreateUser(c *gin.Context) {
    var input models.NewUser

    // クエリパラメータからデータを取得
    input.Name = c.Query("name")
    input.Tel = c.Query("tel")
    input.AccountName = c.Query("account_name")

    // 必須フィールドの検証
    if input.Name == "" || input.Tel == "" || input.AccountName == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "name, tel, account_name は必須です"})
        return
    }

    // 新しいユーザーを作成
    user := models.User{
        Name:        input.Name,
        Tel:         input.Tel,
        AccountName: input.AccountName,
    }

    result := database.DB.Create(&user)
    if result.Error != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "ユーザーの作成に失敗しました"})
        return
    }

    c.JSON(http.StatusCreated, user)
}

// UpdateUser はユーザー情報を更新するハンドラーです
func UpdateUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "無効なIDです"})
        return
    }

    // 既存のユーザーを取得
    var user models.User
    result := database.DB.First(&user, id)
    if result.Error != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "ユーザーが見つかりません"})
        return
    }

    // クエリパラメータから更新データを取得
    name := c.Query("name")
    tel := c.Query("tel")
    accountName := c.Query("account_name")

    // 提供されたフィールドのみ更新
    if name != "" {
        user.Name = name
    }
    if tel != "" {
        user.Tel = tel
    }
    if accountName != "" {
        user.AccountName = accountName
    }

    // データベースを更新
    database.DB.Save(&user)

    c.JSON(http.StatusOK, user)
}

// DeleteUser はユーザーを削除するハンドラーです
func DeleteUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "無効なIDです"})
        return
    }

    result := database.DB.Delete(&models.User{}, id)
    if result.Error != nil || result.RowsAffected == 0 {
        c.JSON(http.StatusNotFound, gin.H{"error": "ユーザーが見つかりません"})
        return
    }

    c.JSON(http.StatusNoContent, nil)
}

メインアプリケーションの更新#

最後に、main.goファイルを更新して、すべてを統合します:

package main

import (
    "fmt"
    "log"

    "go-web-server/database"
    "go-web-server/handlers"

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

func main() {
    // データベースを初期化
    database.InitDatabase()

    // Ginルーターを初期化
    r := gin.Default()

    // APIエンドポイントを設定
    r.GET("/api/users", handlers.GetAllUsers)
    r.GET("/api/users/:id", handlers.GetUser)
    r.GET("/api/users/create", handlers.CreateUser)
    r.GET("/api/users/:id/update", handlers.UpdateUser)
    r.GET("/api/users/:id/delete", handlers.DeleteUser)

    // 基本的なヘルスチェックエンドポイント
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{"text": "こんにちは!"})
    })

    // サーバーを起動
    fmt.Println("サーバーを起動します: http://localhost:8080")
    log.Fatal(r.Run(":8080"))
}

モジュールパスの修正#

モジュールパスをプロジェクトに合わせるため、go.modファイルを編集します。例えば:

module go-web-server

go 1.20

require (
    github.com/gin-gonic/gin v1.9.1
    gorm.io/driver/sqlite v1.5.2
    gorm.io/gorm v1.25.2
    // その他の依存関係...
)

APIの使い方#

サーバーを起動します:

go run main.go

以下のURLでAPIを使用できます:

  • ユーザー作成: http://localhost:8080/api/users/create?name=比古清十郎&tel=000(0000)0000&account_name=hiko
  • ユーザー一覧取得: http://localhost:8080/api/users
  • 特定ユーザー取得: http://localhost:8080/api/users/1 (IDは作成されたユーザーのIDに置き換えてください)
  • ユーザー更新: http://localhost:8080/api/users/1/update?name=弥彦&tel=111(1111)1111 (IDは更新したいユーザーのIDに置き換えてください)
  • ユーザー削除: http://localhost:8080/api/users/1/delete (IDは削除したいユーザーのIDに置き換えてください)

GORM の便利な機能#

GORM にはたくさんの便利な機能があります:

  1. 自動マイグレーション: AutoMigrateを使用してテーブルスキーマを自動作成・更新できます。
  2. 複雑なクエリ: Where, Or, Not, Order などのメソッドを使って複雑なクエリを実行できます。
  3. 関連付け: has one, has many, belongs to, many to many などの関連付けをサポートしています。
  4. フック: BeforeSave, AfterCreate などのフックを使用して、モデルのライフサイクルイベントに処理を追加できます。
  5. トランザクション: Transaction メソッドを使用して、複数の操作をアトミックに実行できます。

例えば、より複雑なクエリは以下のように記述できます:

// 特定の条件に一致するユーザーを検索
var users []models.User
database.DB.Where("name LIKE ?", "%清十郎%").Find(&users)

// 順序付けと制限付きの検索
database.DB.Order("created_at desc").Limit(10).Find(&users)

// 集計関数の使用
var count int64
database.DB.Model(&models.User{}).Count(&count)

これでGoでデータベースを扱うことができました。 次はReactという仕組みを使って画面を作っていきましょう!