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.
GitHub Overview
go-gorm/gorm
The fantastic ORM library for Golang, aims to be developer friendly
Topics
Star History
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)