GORM

GORM is the most popular ORM library for Go, designed with developer experience as the top priority. It adopts a code-first approach and provides a simple, intuitive API. GORM supports multiple databases including MySQL, PostgreSQL, SQLite, and SQL Server, and comes with built-in association, hook, and auto-migration features.

ORMGoDatabaseCode-firstMigration

GitHub Overview

go-gorm/gorm

The fantastic ORM library for Golang, aims to be developer friendly

Stars38,539
Watchers493
Forks4,061
Created:October 25, 2013
Language:Go
License:MIT License

Topics

gogolanggormormweb

Star History

go-gorm/gorm Star History
Data as of: 7/17/2025, 05:31 AM

Library

GORM

Overview

GORM is the most popular ORM library for Go, designed with developer experience as the top priority. It adopts a code-first approach and provides a simple, intuitive API. GORM supports multiple databases including MySQL, PostgreSQL, SQLite, and SQL Server, and comes with built-in association, hook, and auto-migration features.

Details

GORM is a full-featured ORM designed to be "developer friendly." With its code-first approach that automatically generates database schemas from structs, Go developers can maintain their familiar struct-based development style.

Key Features

  • Code-first Approach: Automatic schema generation from structs
  • Full-featured Associations: Supports Has One, Has Many, Belongs To, Many To Many
  • Auto Migration: Automatically applies schema changes
  • Rich Hooks: Lifecycle management with BeforeCreate, AfterCreate, etc.
  • Advanced Querying: Batch processing, prepared statements, Join Preload
  • Extensible Plugin API: Database Resolver, Prometheus, and more

Pros and Cons

Pros

  • Natural syntax following Go language idioms, easy to learn
  • Comprehensive documentation and active community
  • Intuitive struct-based model definition
  • Easy expression of complex associations
  • Improved development efficiency through auto-migration
  • Flexible business logic implementation with hook system

Cons

  • Performance may be lower compared to other ORMs due to heavy use of reflection
  • Limitations in expressing complex SQL queries
  • Tends to have higher memory usage
  • May not be suitable for performance-critical applications

Reference Pages

Code Examples

Basic Setup

# Initialize project
go mod init gorm-example
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql    # For MySQL
go get -u gorm.io/driver/postgres # For PostgreSQL
go get -u gorm.io/driver/sqlite   # For SQLite
package main

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

// Model definition
type User struct {
  gorm.Model
  Name     string
  Email    string `gorm:"uniqueIndex"`
  Age      int
  Posts    []Post
}

type Post struct {
  gorm.Model
  Title    string
  Content  string
  UserID   uint
  User     User
}

func main() {
  // Database connection
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
  if err != nil {
    log.Fatal("failed to connect database")
  }

  // Auto migration
  db.AutoMigrate(&User{}, &Post{})
}

Basic Operations (CRUD)

// Create - Create user
user := User{
  Name:  "John Doe",
  Email: "[email protected]",
  Age:   30,
}
result := db.Create(&user)
fmt.Printf("Created ID: %d, Rows affected: %d\n", user.ID, result.RowsAffected)

// Read - Get user
var user User
db.First(&user, 1)                     // Get by ID
db.First(&user, "name = ?", "John Doe") // Get by condition

// Update - Update user
db.Model(&user).Update("Age", 31)
db.Model(&user).Updates(User{Name: "John Smith", Age: 32})

// Delete - Delete user
db.Delete(&user, 1)

Relations (Associations)

// Create one-to-many relation
user := User{
  Name:  "Jane Smith",
  Email: "[email protected]",
  Posts: []Post{
    {Title: "First Post", Content: "Trying out GORM"},
    {Title: "Second Post", Content: "It's very convenient"},
  },
}
db.Create(&user)

// Get with related data
var userWithPosts User
db.Preload("Posts").First(&userWithPosts, user.ID)

// Many-to-many relation
type User struct {
  gorm.Model
  Name      string
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name  string
  Users []User `gorm:"many2many:user_languages;"`
}

// Add association
var user User
var language Language
db.First(&user, 1)
db.First(&language, 1)
db.Model(&user).Association("Languages").Append(&language)

Transactions

// Basic transaction
err := db.Transaction(func(tx *gorm.DB) error {
  // Create user
  user := User{Name: "Bob Wilson", Email: "[email protected]"}
  if err := tx.Create(&user).Error; err != nil {
    return err
  }

  // Create post
  post := Post{
    Title:   "Transaction Test",
    Content: "Post created within transaction",
    UserID:  user.ID,
  }
  if err := tx.Create(&post).Error; err != nil {
    return err
  }

  // Commit on success
  return nil
})

if err != nil {
  log.Println("Transaction rolled back:", err)
}

// Manual transaction control
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(&post).Error; err != nil {
  tx.Rollback()
  return err
}

tx.Commit()

Hooks (Lifecycle)

import (
  "github.com/google/uuid"
  "errors"
  "time"
)

type User struct {
  ID        uint      `gorm:"primaryKey"`
  UUID      string    `gorm:"uniqueIndex"`
  Name      string
  Email     string
  CreatedAt time.Time
  UpdatedAt time.Time
}

// BeforeCreate hook - before creation
func (u *User) BeforeCreate(tx *gorm.DB) error {
  // Auto-generate UUID
  u.UUID = uuid.New().String()
  
  // Validation
  if len(u.Name) == 0 {
    return errors.New("name is required")
  }
  
  if len(u.Email) == 0 {
    return errors.New("email is required")
  }
  
  return nil
}

// AfterCreate hook - after creation
func (u *User) AfterCreate(tx *gorm.DB) error {
  // Set first user as admin
  if u.ID == 1 {
    return tx.Model(u).Update("role", "admin").Error
  }
  return nil
}

// BeforeUpdate hook - before update
func (u *User) BeforeUpdate(tx *gorm.DB) error {
  // Read-only check
  if u.IsReadOnly() {
    return errors.New("this user is read-only")
  }
  return nil
}

// AfterUpdate hook - after update
func (u *User) AfterUpdate(tx *gorm.DB) error {
  // Update related data
  if u.IsActive {
    return tx.Model(&Profile{}).Where("user_id = ?", u.ID).
      Update("status", "active").Error
  }
  return nil
}

Advanced Features (Batch Processing & Custom Data Types)

// Batch creation
var users []User
for i := 0; i < 1000; i++ {
  users = append(users, User{
    Name:  fmt.Sprintf("User%d", i),
    Email: fmt.Sprintf("user%[email protected]", i),
  })
}

// Create in batches of 100
db.CreateInBatches(users, 100)

// Process large datasets in batches
result := db.Where("age > ?", 18).FindInBatches(&users, 1000, func(tx *gorm.DB, batch int) error {
  for _, user := range users {
    // Process each user
    fmt.Printf("Batch %d: Processing user %s\n", batch, user.Name)
  }
  return nil
})

// Custom data type
import (
  "database/sql/driver"
  "encoding/json"
)

type JSON json.RawMessage

func (j *JSON) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New("type assertion to []byte failed")
  }
  return json.Unmarshal(bytes, j)
}

func (j JSON) Value() (driver.Value, error) {
  if len(j) == 0 {
    return nil, nil
  }
  return json.RawMessage(j).MarshalJSON()
}

type User struct {
  gorm.Model
  Name     string
  Metadata JSON `gorm:"type:json"`
}

// Prepared statements
db.Session(&gorm.Session{PrepareStmt: true})

// Conditional updates
db.Model(&user).Where("active = ?", true).Update("last_login", time.Now())

// Raw SQL execution
type Result struct {
  Name  string
  Total int
}

var results []Result
db.Raw("SELECT name, COUNT(*) as total FROM users GROUP BY name").Scan(&results)