Go の Gin と GORM の連携
Gin と GORM を組み合わせると、Web API とデータベースを連携させたアプリケーションを効率的に構築できる。ここでは実践的な CRUD API の実装を解説する。
プロジェクト構成
├── main.go
├── models/
│ └── user.go
├── handlers/
│ └── user.go
└── database/
└── database.goデータベース接続の設定
// database/database.go
package database
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Connect() error {
dsn := "user:password@tcp(localhost:3306)/myapp?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
DB = db
return nil
}モデルの定義
// models/user.go
package models
import "gorm.io/gorm"
type User struct {
gorm.Model
Name string `json:"name" gorm:"size:100;not null"`
Email string `json:"email" gorm:"size:255;uniqueIndex;not null"`
Age int `json:"age"`
}gorm.Model を埋め込むと、ID、CreatedAt、UpdatedAt、DeletedAt フィールドが自動で追加される。
ハンドラの実装
// handlers/user.go
package handlers
import (
"net/http"
"myapp/database"
"myapp/models"
"github.com/gin-gonic/gin"
)
// ユーザー一覧取得
func GetUsers(c *gin.Context) {
var users []models.User
result := database.DB.Find(&users)
if result.Error != nil {
c.JSON(500, gin.H{"error": "Failed to fetch users"})
return
}
c.JSON(200, users)
}
// ユーザー取得
func GetUser(c *gin.Context) {
id := c.Param("id")
var user models.User
result := database.DB.First(&user, id)
if result.Error != nil {
c.JSON(404, gin.H{"error": "User not found"})
return
}
c.JSON(200, user)
}作成と更新
type CreateUserInput struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0"`
}
// ユーザー作成
func CreateUser(c *gin.Context) {
var input CreateUserInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
user := models.User{
Name: input.Name,
Email: input.Email,
Age: input.Age,
}
result := database.DB.Create(&user)
if result.Error != nil {
c.JSON(500, gin.H{"error": "Failed to create user"})
return
}
c.JSON(201, user)
}
type UpdateUserInput struct {
Name string `json:"name"`
Email string `json:"email" binding:"omitempty,email"`
Age int `json:"age" binding:"omitempty,gte=0"`
}
// ユーザー更新
func UpdateUser(c *gin.Context) {
id := c.Param("id")
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
c.JSON(404, gin.H{"error": "User not found"})
return
}
var input UpdateUserInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
database.DB.Model(&user).Updates(input)
c.JSON(200, user)
}削除
func DeleteUser(c *gin.Context) {
id := c.Param("id")
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
c.JSON(404, gin.H{"error": "User not found"})
return
}
database.DB.Delete(&user)
c.JSON(200, gin.H{"message": "User deleted"})
}GORM のデフォルトは論理削除(DeletedAt に日時をセット)だ。物理削除したい場合は Unscoped().Delete() を使う。
メインファイル
// main.go
package main
import (
"log"
"myapp/database"
"myapp/handlers"
"myapp/models"
"github.com/gin-gonic/gin"
)
func main() {
// DB接続
if err := database.Connect(); err != nil {
log.Fatal("Failed to connect database:", err)
}
// マイグレーション
database.DB.AutoMigrate(&models.User{})
r := gin.Default()
// ルーティング
users := r.Group("/users")
{
users.GET("", handlers.GetUsers)
users.GET("/:id", handlers.GetUser)
users.POST("", handlers.CreateUser)
users.PUT("/:id", handlers.UpdateUser)
users.DELETE("/:id", handlers.DeleteUser)
}
r.Run(":8080")
}ページネーション
一覧取得にページネーションを追加する。
func GetUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
offset := (page - 1) * limit
var users []models.User
var total int64
database.DB.Model(&models.User{}).Count(&total)
database.DB.Offset(offset).Limit(limit).Find(&users)
c.JSON(200, gin.H{
"data": users,
"total": total,
"page": page,
"limit": limit,
})
}トランザクション
複数のデータベース操作をまとめて行う場合はトランザクションを使う。
func TransferPoints(c *gin.Context) {
var input TransferInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
err := database.DB.Transaction(func(tx *gorm.DB) error {
// 送信元からポイント減算
if err := tx.Model(&models.User{}).
Where("id = ?", input.FromID).
Update("points", gorm.Expr("points - ?", input.Amount)).Error; err != nil {
return err
}
// 送信先にポイント加算
if err := tx.Model(&models.User{}).
Where("id = ?", input.ToID).
Update("points", gorm.Expr("points + ?", input.Amount)).Error; err != nil {
return err
}
return nil
})
if err != nil {
c.JSON(500, gin.H{"error": "Transfer failed"})
return
}
c.JSON(200, gin.H{"message": "Transfer successful"})
}トランザクション内でエラーを返すと自動的にロールバックされる。Gin と GORM の組み合わせは Go での Web API 開発において定番の構成だ。両者の機能を理解し、適切に連携させることで、保守性の高いアプリケーションが構築できる。