Go言語で学ぶWebアプリケーション開発2：[データベース連携&認証機能の追加]

## はじめに

前回の記事では、Go + Ginを使ったバックエンドとReact + Viteを使ったフロントエンドで作成したシンプルなToDoアプリを作成しました。  
今回はそこに、データベース連携（SQLite + GORM）とJWT認証機能の追加を行い、より実用的なWebアプリケーションに発展させます。

**対象読者**
- Goでデータベースと連携したWebアプリを作成したい方
- JWT認証を導入し、ログイン機能を実装したい方
- フルスタック開発を学びたい方

**目次**
1. データベース連携（GORM + SQLite）
   - GORMのセットアップ
   - モデルの定義とマイグレーション
   - CRUD操作の実装
2. 認証機能（JWT）
   - ユーザーモデルの作成
   - ログインAPIの実装
   - JWTトークンの発行と認証
3. フロントエンドとの統合
   - Reactからの認証リクエスト
   - 認証されたユーザーのみAPIにアクセス可能にする

---

## 1. データベース連携（GORM + SQLite）

**1.1 GORM のセットアップ**

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

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

**1.2 モデルの定義とマイグレーション**

データベースに格納するTodoモデルを定義します。

**models/todo.go**
```go
package models

import "gorm.io/gorm"

type Todo struct {
    ID     uint   `gorm:"primaryKey"`
    Task   string `json:"task"`
    Status bool   `json:"status"`
}
```

データベースの接続とマイグレーションを行います。

**database/database.go**
```go
package database

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "log"
    "models"
)

var DB *gorm.DB

func InitDatabase() {
    var err error
    DB, err = gorm.Open(sqlite.Open("app.db"), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect database")
    }
    DB.AutoMigrate(&models.Todo{})
}
```

main.goでデータベースを初期化します。

```go
database.InitDatabase()
```

**1.3 CRUD操作の実装**

**controllers/todo_controller.go**
```go
package controllers

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

func GetTodos(c *gin.Context) {
    var todos []models.Todo
    database.DB.Find(&todos)
    c.JSON(http.StatusOK, todos)
}

func CreateTodo(c *gin.Context) {
    var newTodo models.Todo
    if err := c.ShouldBindJSON(&newTodo); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    database.DB.Create(&newTodo)
    c.JSON(http.StatusCreated, newTodo)
}
```

ルーティングの設定を行います。

```go
router.GET("/api/todos", controllers.GetTodos)
router.POST("/api/todos", controllers.CreateTodo)
```

---

## 2. 認証機能（JWT）

**2.1 ユーザーモデルの作成**

**models/user.go**
```go
package models

type User struct {
    ID       uint   `gorm:"primaryKey"`
    Username string `json:"username"`
    Password string `json:"password"`
}
```

**2.2 ログインAPIの実装**

**controllers/auth_controller.go**
```go
package controllers

import (
    "github.com/gin-gonic/gin"
    "net/http"
    "database"
    "models"
    "time"
    "github.com/golang-jwt/jwt/v4"
)

type LoginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

func Login(c *gin.Context) {
    var req LoginRequest
    var user models.User
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    database.DB.Where("username = ?", req.Username).First(&user)
    if user.ID == 0 || user.Password != req.Password {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
        return
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "username": user.Username,
        "exp":     time.Now().Add(time.Hour * 2).Unix(),
    })
    tokenString, _ := token.SignedString([]byte("secret"))
    c.JSON(http.StatusOK, gin.H{"token": tokenString})
}
```

ルーティングを設定します。

```go
router.POST("/api/login", controllers.Login)
```

---

## 3. フロントエンドとの統合

**3.1 React からの認証リクエスト**
**src/Login.jsx**
```jsx
import { useState } from "react";
import axios from "axios";

function Login() {
    const [username, setUsername] = useState("");
    const [password, setPassword] = useState("");
    
    const handleLogin = async () => {
        const response = await axios.post("http://localhost:8080/api/login", { username, password });
        localStorage.setItem("token", response.data.token);
    };
    
    return (
        <div>
            <input type="text" placeholder="Username" onChange={(e) => setUsername(e.target.value)} />
            <input type="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} />
            <button onClick={handleLogin}>Login</button>
        </div>
    );
}

export default Login;
```

---

## まとめ

今回は、GORMを使ったデータベース連携とJWT認証機能の追加を行い、より実用的なWebアプリケーションに近づけました。  
次はさらに**ユーザー管理やロールベース認証の追加** をまとめていこうと思います！