GORM setup, database connections, models, CRUD operations, associations, migrations, and query patterns reference.
GORM Developer Guide
Section titled “GORM Developer Guide”Installation & Setup
Section titled “Installation & Setup”go get -u gorm.io/gormgo get -u gorm.io/driver/sqlite # SQLitego get -u gorm.io/driver/postgres # PostgreSQLgo get -u gorm.io/driver/mysql # MySQLgo get -u gorm.io/driver/sqlserver # SQL ServerConnect to Database
Section titled “Connect to Database”import ( "gorm.io/driver/postgres" "gorm.io/driver/mysql" "gorm.io/driver/sqlite" "gorm.io/gorm")
// SQLitedb, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// PostgreSQLdsn := "host=localhost user=postgres password=secret dbname=mydb port=5432 sslmode=disable"db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
// MySQLdsn := "user:password@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// With Loggerdb, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), // log all SQL})Defining Models
Section titled “Defining Models”import "gorm.io/gorm"
// Basic model — gorm.Model adds ID, CreatedAt, UpdatedAt, DeletedAttype User struct { gorm.Model Name string Email string `gorm:"uniqueIndex"` Age int}
// Custom primary keytype Product struct { ID uint `gorm:"primaryKey"` Code string `gorm:"uniqueIndex;size:100"` Price float64}
// All common struct tagstype Example struct { ID uint `gorm:"primaryKey;autoIncrement"` Name string `gorm:"column:full_name;size:255;not null"` Email string `gorm:"uniqueIndex"` Age int `gorm:"default:18"` Bio string `gorm:"type:text"` Active bool `gorm:"default:true"` Score float64 `gorm:"precision:2"` Ignored string `gorm:"-"` // skip this field CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` // soft delete}Auto Migrate
Section titled “Auto Migrate”db.AutoMigrate(&User{}) // migrate single modeldb.AutoMigrate(&User{}, &Product{}, &Order{}) // migrate multiple modelsCreate Records
Section titled “Create Records”user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
result := db.Create(&user) // insert and populate IDresult.Error // check for errorsresult.RowsAffected // number of rows inserted
// Create with selected fields onlydb.Select("Name", "Email").Create(&user)
// Batch insertusers := []User{ {Name: "Alice"}, {Name: "Bob"}, {Name: "Charlie"},}db.Create(&users)
// Create with mapdb.Model(&User{}).Create(map[string]interface{}{ "Name": "Dave", "Email": "dave@example.com",})Query Records
Section titled “Query Records”var user Uservar users []User
db.First(&user) // ORDER BY id LIMIT 1db.Last(&user) // ORDER BY id DESC LIMIT 1db.Take(&user) // no order, LIMIT 1
db.First(&user, 10) // find by primary key = 10db.First(&user, "email = ?", "alice@example.com") // inline condition
db.Find(&users) // get all recordsdb.Find(&users, []int{1, 2, 3}) // find by slice of primary keys
// Wheredb.Where("name = ?", "Alice").First(&user)db.Where("age > ? AND active = ?", 18, true).Find(&users)db.Where("name IN ?", []string{"Alice", "Bob"}).Find(&users)db.Where("name LIKE ?", "%ali%").Find(&users)db.Where("created_at BETWEEN ? AND ?", startDate, endDate).Find(&users)
// Where with struct (only non-zero fields are used)db.Where(&User{Name: "Alice", Age: 30}).Find(&users)
// Where with map (zero values ARE included)db.Where(map[string]interface{}{"name": "Alice", "age": 0}).Find(&users)
// Not / Ordb.Not("name = ?", "Alice").Find(&users)db.Where("name = ?", "Alice").Or("name = ?", "Bob").Find(&users)
// Select specific columnsdb.Select("name", "email").Find(&users)
// Order, Limit, Offsetdb.Order("age desc").Find(&users)db.Limit(10).Offset(20).Find(&users) // page 3 of 10
// Distinctdb.Distinct("name", "age").Find(&users)
// Countvar count int64db.Model(&User{}).Where("active = ?", true).Count(&count)
// Pluck (single column into slice)var names []stringdb.Model(&User{}).Pluck("name", &names)Update Records
Section titled “Update Records”// Save — updates all fields (including zero values)db.Save(&user)
// Update single columndb.Model(&user).Update("name", "NewName")
// Update multiple columns with struct (skips zero values)db.Model(&user).Updates(User{Name: "New", Age: 25})
// Update multiple columns with map (includes zero values)db.Model(&user).Updates(map[string]interface{}{ "name": "New", "age": 0, "active": false,})
// Update with condition (no model)db.Model(&User{}).Where("active = ?", false).Update("name", "Inactive")
// Update selected columns onlydb.Model(&user).Select("name", "age").Updates(User{Name: "Alice", Age: 0})
// Omit specific columnsdb.Model(&user).Omit("age").Updates(User{Name: "Alice", Age: 99}) // age not updated
// Increment / Decrement (atomic)db.Model(&user).Update("score", gorm.Expr("score + ?", 10))Delete Records
Section titled “Delete Records”// Soft delete (sets DeletedAt — requires gorm.Model or gorm.DeletedAt field)db.Delete(&user)db.Delete(&user, 10) // by primary keydb.Where("age < ?", 18).Delete(&User{}) // conditional delete
// Find soft-deleted recordsdb.Unscoped().Where("name = ?", "Alice").Find(&users)
// Permanently delete (hard delete, bypasses soft delete)db.Unscoped().Delete(&user)
// Batch deletedb.Where("active = ?", false).Delete(&User{})Associations
Section titled “Associations”// One-to-Manytype User struct { gorm.Model Name string CreditCards []CreditCard // has many}type CreditCard struct { gorm.Model Number string UserID uint // foreign key (convention: OwnerType + ID)}
// Belongs-Totype Order struct { gorm.Model UserID uint User User // belongs to}
// Many-to-Manytype User struct { gorm.Model Languages []Language `gorm:"many2many:user_languages;"` // join table}type Language struct { gorm.Model Name string}
// Has-Onetype User struct { gorm.Model Profile Profile}type Profile struct { gorm.Model Bio string UserID uint}Preloading (Eager Loading)
Section titled “Preloading (Eager Loading)”// Preload single associationdb.Preload("CreditCards").Find(&users)
// Preload nested associationsdb.Preload("Orders.Products").Find(&users)
// Preload with conditionsdb.Preload("CreditCards", "number = ?", "1234").Find(&users)
// Preload all associationsdb.Preload(clause.Associations).Find(&users)
// Joins (SQL JOIN instead of separate query)db.Joins("Profile").Find(&users)db.Joins("JOIN orders ON orders.user_id = users.id").Find(&users)Association Operations
Section titled “Association Operations”// Append to has-manydb.Model(&user).Association("CreditCards").Append(&CreditCard{Number: "9999"})
// Replace all associationsdb.Model(&user).Association("Languages").Replace([]Language{langEN, langZH})
// Delete specific association recorddb.Model(&user).Association("CreditCards").Delete(&card)
// Clear all associations (without deleting records)db.Model(&user).Association("Languages").Clear()
// Count associationsdb.Model(&user).Association("CreditCards").Count()Raw SQL & Exec
Section titled “Raw SQL & Exec”// Raw query into struct/slicevar users []Userdb.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)
// Raw into mapvar result map[string]interface{}db.Raw("SELECT name, age FROM users WHERE id = ?", 1).Scan(&result)
// Exec (INSERT / UPDATE / DELETE without returning rows)db.Exec("UPDATE users SET active = ? WHERE id = ?", true, 1)
// Named argsdb.Where("name = @name AND age = @age", sql.Named("name", "Alice"), sql.Named("age", 30)).Find(&users)db.Raw("SELECT * FROM users WHERE name = @name", map[string]interface{}{"name": "Alice"}).Scan(&users)Transactions
Section titled “Transactions”// Auto transaction (rollback on error)err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&User{Name: "Alice"}).Error; err != nil { return err // triggers rollback } if err := tx.Create(&Order{UserID: 1}).Error; err != nil { return err } return nil // commit})
// Manual transactiontx := db.Begin()if tx.Error != nil { /* handle */ }
tx.Create(&User{Name: "Alice"})tx.Create(&Order{UserID: 1})
if someCondition { tx.Rollback()} else { tx.Commit()}
// SavePointtx := db.Begin()tx.Create(&user1)tx.SavePoint("sp1")tx.Create(&user2)tx.RollbackTo("sp1") // rolls back user2 onlytx.Commit()Scopes (Reusable Query Conditions)
Section titled “Scopes (Reusable Query Conditions)”// Define reusable scopesfunc ActiveUsers(db *gorm.DB) *gorm.DB { return db.Where("active = ?", true)}
func OlderThan(age int) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Where("age > ?", age) }}
func Paginate(page, size int) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { offset := (page - 1) * size return db.Offset(offset).Limit(size) }}
// Use scopesdb.Scopes(ActiveUsers, OlderThan(18), Paginate(2, 10)).Find(&users)Hooks (Lifecycle Callbacks)
Section titled “Hooks (Lifecycle Callbacks)”// Available hooks (Before/After for Create, Save, Update, Delete, Find)func (u *User) BeforeCreate(tx *gorm.DB) error { u.Name = strings.TrimSpace(u.Name) return nil}
func (u *User) AfterCreate(tx *gorm.DB) error { log.Printf("User %d created", u.ID) return nil}
func (u *User) BeforeDelete(tx *gorm.DB) error { if u.Admin { return errors.New("cannot delete admin users") } return nil}
// Hooks run inside the same transaction automatically// Return error to abort and rollbackIndexes & Constraints
Section titled “Indexes & Constraints”type User struct { gorm.Model Name string `gorm:"index"` // simple index Email string `gorm:"uniqueIndex"` // unique index City string `gorm:"index:idx_city_age"` // composite index (part 1) Age int `gorm:"index:idx_city_age"` // composite index (part 2) Code string `gorm:"index:,sort:desc,type:btree"` // index options Ref string `gorm:"check:ref_check,ref <> 'bad'"` // check constraint}
// Create index manuallydb.Exec("CREATE INDEX idx_name ON users(name)")Aggregations & Group By
Section titled “Aggregations & Group By”type Result struct { Date string Total int}
// Group by with aggregationdb.Model(&Order{}). Select("date(created_at) AS date, SUM(amount) AS total"). Group("date(created_at)"). Having("SUM(amount) > ?", 100). Scan(&results)
// Multiple aggregationsdb.Model(&User{}). Select("age, count(*) as count, avg(score) as avg_score"). Group("age"). Scan(&results)Connection Pool
Section titled “Connection Pool”sqlDB, err := db.DB() // get underlying *sql.DB
sqlDB.SetMaxIdleConns(10) // max idle connectionssqlDB.SetMaxOpenConns(100) // max open connectionssqlDB.SetConnMaxLifetime(time.Hour) // max connection lifetimesqlDB.SetConnMaxIdleTime(10 * time.Minute)
sqlDB.Stats() // pool statisticssqlDB.Ping() // check connectivitysqlDB.Close() // close poolSession & Context
Section titled “Session & Context”// Reusable session (does not share state)tx := db.Session(&gorm.Session{})tx.Where("name = ?", "Alice").Find(&users)
// Dry run (build SQL without executing)stmt := db.Session(&gorm.Session{DryRun: true}).Find(&users)stmt.Statement.SQL.String() // print the SQL
// WithContext (cancellation / tracing)ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()db.WithContext(ctx).Find(&users)
// PrepareStmt (cache prepared statements)db.Session(&gorm.Session{PrepareStmt: true}).Find(&users)Error Handling
Section titled “Error Handling”// Always check .Errorresult := db.First(&user, 10)if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { // no record found } log.Fatal(result.Error)}
// Common GORM errorsgorm.ErrRecordNotFound // First/Last/Take found no rowgorm.ErrInvalidTransaction // invalid transaction stategorm.ErrNotImplemented // feature not implemented by drivergorm.ErrMissingWhereClause // update/delete without WHERE (safety guard)gorm.ErrUnsupportedRelation // unsupported association typegorm.ErrDryRunModeUnsupportedUseful Config Options
Section titled “Useful Config Options”db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ TablePrefix: "app_", // prefix all table names SingularTable: true, // use singular table names (user vs users) }, DisableForeignKeyConstraintWhenMigrating: true, SkipDefaultTransaction: true, // improve perf for single writes QueryFields: true, // SELECT all named fields instead of SELECT * CreateBatchSize: 1000, // default batch size for bulk inserts Logger: logger.Default.LogMode(logger.Silent), // suppress logs})Custom Data Types
Section titled “Custom Data Types”import "database/sql/driver"
// Custom JSON typetype JSON json.RawMessage
func (j *JSON) Scan(value interface{}) error { /* parse bytes into j */ }func (j JSON) Value() (driver.Value, error) { return string(j), nil }
// Custom Encrypted typetype Encrypted string
func (e *Encrypted) Scan(value interface{}) error { // decrypt value from DB *e = Encrypted(decrypt(value.(string))) return nil}func (e Encrypted) Value() (driver.Value, error) { return encrypt(string(e)), nil}
// Use in modeltype User struct { gorm.Model Settings JSON Token Encrypted}Clauses & Advanced Queries
Section titled “Clauses & Advanced Queries”import "gorm.io/gorm/clause"
// Lockingdb.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) // SELECT ... FOR UPDATEdb.Clauses(clause.Locking{Strength: "SHARE"}).Find(&users) // SELECT ... FOR SHARE
// ON CONFLICT (Upsert)db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user) // ignore duplicates
db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "email"}}, DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),}).Create(&user)
// Returning (PostgreSQL)db.Clauses(clause.Returning{}).Create(&user) // populate all fields after insertdb.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).Create(&user)Logger & Debugging
Section titled “Logger & Debugging”// Print SQL without executingstmt := db.Session(&gorm.Session{DryRun: true}). Where("age > ?", 18).Find(&User{})fmt.Println(stmt.Statement.SQL.String())fmt.Println(stmt.Statement.Vars)
// Debug mode (print all SQL for this query)db.Debug().Where("name = ?", "Alice").Find(&users)
// Custom loggernewLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: 200 * time.Millisecond, // warn on slow queries LogLevel: logger.Warn, IgnoreRecordNotFoundError: true, Colorful: true, },)db, _ = gorm.Open(sqlite.Open("test.db"), &gorm.Config{Logger: newLogger})Polymorphic Associations
Section titled “Polymorphic Associations”type Dog struct { gorm.Model Name string Toys []Toy `gorm:"polymorphic:Owner;"`}
type Cat struct { gorm.Model Name string Toys []Toy `gorm:"polymorphic:Owner;"`}
type Toy struct { gorm.Model Name string OwnerID uint OwnerType string // stores "dogs" or "cats"}
// GORM auto-sets OwnerType on create/querydb.Create(&Dog{Name: "Rex", Toys: []Toy{{Name: "Ball"}}})db.Model(&dog).Association("Toys").Find(&toys)Table & Column Customization
Section titled “Table & Column Customization”// Custom table name via methodfunc (User) TableName() string { return "app_users" }
// Query a different table at runtimedb.Table("archived_users").Find(&users)db.Table("users").Select("name, email").Scan(&results)
// Use a subquery as tablesubQuery := db.Select("AVG(age) as avg_age").Table("users")db.Select("*").Table("(?) AS u", subQuery).Where("u.avg_age > ?", 25).Scan(&results)Soft Delete Customization
Section titled “Soft Delete Customization”import "gorm.io/plugin/soft_delete"
type User struct { gorm.Model Name string DeletedAt soft_delete.DeletedAt // Unix timestamp // OR IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"` // 0/1 flag}
// Query includes deleted with Unscopeddb.Unscoped().Find(&users) // includes soft-deleted rowsUseful One-Liners
Section titled “Useful One-Liners”// First or createdb.FirstOrCreate(&user, User{Email: "alice@example.com"})
// First or init (does not save)db.FirstOrInit(&user, User{Email: "alice@example.com"})
// Assign extra attrs when founddb.Where(User{Email: "alice@example.com"}).Assign(User{Name: "Alice"}).FirstOrCreate(&user)
// Find and update in one calldb.Model(&user).Where("id = ?", 1).Update("name", "Alice")
// Rows (iterate large result sets without loading all into memory)rows, _ := db.Model(&User{}).Where("active = ?", true).Rows()defer rows.Close()for rows.Next() { var u User db.ScanRows(rows, &u) // process u}
// Check if record existsvar exists booldb.Model(&User{}).Select("count(*) > 0").Where("email = ?", "alice@example.com").Find(&exists)