Back to all posts
August 23, 2025Charlie BrownDevelopment

GORM Database Operations in Golang: Advanced Patterns

Master GORM for database operations in Golang, including associations, transactions, migrations, query optimization, and advanced query patterns.

GORM Database Operations in Golang: Advanced Patterns

GORM Database Operations in Golang: Advanced Patterns

GORM is the most popular ORM for Golang, providing a developer-friendly way to interact with databases. This article explores advanced GORM patterns, including associations, transactions, migrations, and performance optimization.

Getting Started

Installation

bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
# or
go get -u gorm.io/driver/mysql
go get -u gorm.io/driver/sqlite

Basic Setup

go
package main

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

func initDB() (*gorm.DB, error) {
    dsn := "host=localhost user=postgres password=postgres dbname=mydb port=5432 sslmode=disable TimeZone=Asia/Shanghai"
    
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    
    if err != nil {
        return nil, err
    }
    
    return db, nil
}

Model Definition

Basic Models

go
package models

import (
    "time"
    "gorm.io/gorm"
)

type User struct {
    ID        uint           `gorm:"primaryKey" json:"id"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
    
    FirstName string `gorm:"type:varchar(100);not null" json:"first_name"`
    LastName  string `gorm:"type:varchar(100);not null" json:"last_name"`
    Email     string `gorm:"type:varchar(255);uniqueIndex;not null" json:"email"`
    Age       int    `gorm:"type:integer" json:"age"`
    
    // Relationships
    Orders    []Order    `gorm:"foreignKey:UserID" json:"orders"`
    Profile   Profile    `gorm:"foreignKey:UserID" json:"profile"`
}

type Order struct {
    ID        uint      `gorm:"primaryKey" json:"id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
    
    UserID    uint    `gorm:"not null;index" json:"user_id"`
    User      User    `gorm:"foreignKey:UserID" json:"user"`
    
    Product   string  `gorm:"type:varchar(255)" json:"product"`
    Amount    float64 `gorm:"type:decimal(10,2)" json:"amount"`
    Status    string  `gorm:"type:varchar(50);default:'pending'" json:"status"`
    
    OrderItems []OrderItem `gorm:"foreignKey:OrderID" json:"order_items"`
}

type Profile struct {
    ID        uint      `gorm:"primaryKey" json:"id"`
    UserID    uint      `gorm:"uniqueIndex;not null" json:"user_id"`
    User      User      `gorm:"foreignKey:UserID" json:"user"`
    
    Bio       string    `gorm:"type:text" json:"bio"`
    Avatar    string    `gorm:"type:varchar(255)" json:"avatar"`
}

CRUD Operations

Create

go
// Single create
user := User{
    FirstName: "John",
    LastName:  "Doe",
    Email:     "john@example.com",
    Age:       30,
}
result := db.Create(&user)

// Batch create
users := []User{
    {FirstName: "Jane", LastName: "Smith", Email: "jane@example.com"},
    {FirstName: "Bob", LastName: "Johnson", Email: "bob@example.com"},
}
db.Create(&users)

// Create with selected fields
db.Select("FirstName", "LastName", "Email").Create(&user)

// Create with omitted fields
db.Omit("Age").Create(&user)

Read

go
// Find by ID
var user User
db.First(&user, 1) // WHERE id = 1
db.First(&user, "id = ?", 1)

// Find all
var users []User
db.Find(&users)

// Conditions
db.Where("age > ?", 18).Find(&users)
db.Where("first_name = ? AND age > ?", "John", 18).Find(&users)

// Or conditions
db.Where("first_name = ?", "John").Or("last_name = ?", "Doe").Find(&users)

// Not conditions
db.Not("age = ?", 18).Find(&users)

// In
db.Where("age IN ?", []int{18, 20, 22}).Find(&users)

// Like
db.Where("email LIKE ?", "%@example.com").Find(&users)

// Order
db.Order("age desc").Find(&users)
db.Order("age desc, first_name asc").Find(&users)

// Limit and Offset
db.Limit(10).Offset(20).Find(&users)

// Count
var count int64
db.Model(&User{}).Where("age > ?", 18).Count(&count)

Update

go
// Update single field
db.Model(&user).Update("age", 31)

// Update multiple fields
db.Model(&user).Updates(User{Age: 31, Email: "newemail@example.com"})

// Update with map
db.Model(&user).Updates(map[string]interface{}{
    "age": 31,
    "email": "newemail@example.com",
})

// Update selected fields
db.Model(&user).Select("age").Updates(User{Age: 31, Email: "ignored"})

// Update without hooks
db.Model(&user).UpdateColumn("age", 31)

// Batch update
db.Model(&User{}).Where("age < ?", 18).Update("age", 18)

Delete

go
// Soft delete
db.Delete(&user)

// Hard delete
db.Unscoped().Delete(&user)

// Delete by ID
db.Delete(&User{}, 1)

// Batch delete
db.Where("age < ?", 18).Delete(&User{})

Associations

Belongs To

go
type Order struct {
    UserID uint
    User   User `gorm:"foreignKey:UserID"`
}

// Preload
var order Order
db.Preload("User").First(&order, 1)

// Eager loading
db.Preload("User.Profile").First(&order, 1)

Has One

go
type User struct {
    Profile Profile `gorm:"foreignKey:UserID"`
}

// Preload
var user User
db.Preload("Profile").First(&user, 1)

Has Many

go
type User struct {
    Orders []Order `gorm:"foreignKey:UserID"`
}

// Preload
var user User
db.Preload("Orders").First(&user, 1)

// Preload with conditions
db.Preload("Orders", "status = ?", "completed").First(&user, 1)

Many to Many

go
type User struct {
    Roles []Role `gorm:"many2many:user_roles;"`
}

type Role struct {
    ID   uint
    Name string
    Users []User `gorm:"many2many:user_roles;"`
}

// Create with associations
user := User{
    FirstName: "John",
    Roles: []Role{
        {Name: "admin"},
        {Name: "user"},
    },
}
db.Create(&user)

// Append associations
var user User
var role Role
db.First(&user, 1)
db.First(&role, 1)
db.Model(&user).Association("Roles").Append(&role)

// Replace associations
db.Model(&user).Association("Roles").Replace(&roles)

// Delete associations
db.Model(&user).Association("Roles").Delete(&role)

// Clear associations
db.Model(&user).Association("Roles").Clear()

// Count associations
count := db.Model(&user).Association("Roles").Count()

Transactions

Basic Transaction

go
// Manual transaction
tx := db.Begin()
defer func() {
    if r := recover(); r != nil {
        tx.Rollback()
    }
}()

if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}

if err := tx.Create(&order).Error; err != nil {
    tx.Rollback()
    return err
}

return tx.Commit().Error

Transaction Function

go
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err
    }
    
    if err := tx.Create(&order).Error; err != nil {
        return err
    }
    
    return nil
})

Nested Transactions

go
db.Transaction(func(tx1 *gorm.DB) error {
    tx1.Create(&user)
    
    tx1.Transaction(func(tx2 *gorm.DB) error {
        tx2.Create(&order)
        return nil
    })
    
    return nil
})

Migrations

Auto Migrate

go
// Auto migrate
err := db.AutoMigrate(&User{}, &Order{}, &Profile{})

// Auto migrate with options
err := db.AutoMigrate(&User{}, &Order{})

Manual Migrations

go
// Create table
db.Exec(`
    CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,
        first_name VARCHAR(100) NOT NULL,
        last_name VARCHAR(100) NOT NULL,
        email VARCHAR(255) UNIQUE NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
`)

// Add column
db.Exec("ALTER TABLE users ADD COLUMN age INTEGER")

// Drop column
db.Exec("ALTER TABLE users DROP COLUMN age")

Query Optimization

Select Specific Fields

go
// Select only needed fields
db.Select("id", "first_name", "email").Find(&users)

// Omit fields
db.Omit("created_at", "updated_at").Find(&users)

Preloading Optimization

go
// Preload with select
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
    return db.Select("id", "user_id", "amount")
}).Find(&users)

// Preload with conditions
db.Preload("Orders", "status = ?", "completed").Find(&users)

Joins

go
// Inner join
db.Joins("User").Find(&orders)

// Left join
db.Joins("LEFT JOIN users ON orders.user_id = users.id").Find(&orders)

// Join with conditions
db.Joins("User").Where("users.age > ?", 18).Find(&orders)

Raw Queries

go
// Raw SQL
var users []User
db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)

// Exec raw SQL
db.Exec("UPDATE users SET age = ? WHERE id = ?", 31, 1)

// Named arguments
db.Raw("SELECT * FROM users WHERE age > @age", sql.Named("age", 18)).Scan(&users)

Hooks

Model Hooks

go
type User struct {
    // ... fields
}

// BeforeCreate hook
func (u *User) BeforeCreate(tx *gorm.DB) error {
    // Hash password, set defaults, etc.
    return nil
}

// AfterCreate hook
func (u *User) AfterCreate(tx *gorm.DB) error {
    // Send welcome email, etc.
    return nil
}

// BeforeUpdate hook
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    // Update timestamps, etc.
    return nil
}

Scopes

Define Scopes

go
// Scope function
func AdultUsers(db *gorm.DB) *gorm.DB {
    return db.Where("age >= ?", 18)
}

func ActiveUsers(db *gorm.DB) *gorm.DB {
    return db.Where("deleted_at IS NULL")
}

// Use scopes
db.Scopes(AdultUsers, ActiveUsers).Find(&users)

// Chain scopes
db.Scopes(AdultUsers).Scopes(ActiveUsers).Find(&users)

Best Practices

  1. Use Transactions: For related operations
  2. Optimize Queries: Select only needed fields
  3. Use Indexes: For frequently queried fields
  4. Batch Operations: For bulk inserts/updates
  5. Connection Pooling: Configure properly
  6. Error Handling: Always check errors
  7. Use Preload: Instead of N+1 queries

Conclusion

GORM provides powerful features for database operations in Golang. By mastering associations, transactions, migrations, and query optimization, you can build efficient, maintainable database layers. Remember to use transactions for related operations, optimize queries, and leverage GORM's hooks and scopes for cleaner code.

References

Want more insights?

Subscribe to our newsletter or follow us for more updates on software development and team scaling.

Contact Us