GORM's Hooks mechanism allows executing custom logic at different stages of database operations. Hook functions are automatically called before or after specific operations.
Hook Types
Object-level Hooks
These hooks are triggered at the object level and apply to operations on single objects.
gotype User struct { gorm.Model Name string Email string Age int } // Before create hook func (u *User) BeforeCreate(tx *gorm.DB) error { fmt.Println("BeforeCreate: Preparing to create user") if u.Name == "" { return errors.New("Username cannot be empty") } return nil } // After create hook func (u *User) AfterCreate(tx *gorm.DB) error { fmt.Println("AfterCreate: User created successfully") return nil } // Before update hook func (u *User) BeforeUpdate(tx *gorm.DB) error { fmt.Println("BeforeUpdate: Preparing to update user") return nil } // After update hook func (u *User) AfterUpdate(tx *gorm.DB) error { fmt.Println("AfterUpdate: User updated successfully") return nil } // Before save hook (triggers for both Create and Update) func (u *User) BeforeSave(tx *gorm.DB) error { fmt.Println("BeforeSave: Preparing to save user") return nil } // After save hook (triggers for both Create and Update) func (u *User) AfterSave(tx *gorm.DB) error { fmt.Println("AfterSave: User saved successfully") return nil } // Before delete hook func (u *User) BeforeDelete(tx *gorm.DB) error { fmt.Println("BeforeDelete: Preparing to delete user") return nil } // After delete hook func (u *User) AfterDelete(tx *gorm.DB) error { fmt.Println("AfterDelete: User deleted successfully") return nil } // After find hook func (u *User) AfterFind(tx *gorm.DB) error { fmt.Println("AfterFind: User queried successfully") return nil }
Query-level Hooks
These hooks are triggered at the query level and apply to batch operations.
go// Before query hook func (u *User) BeforeQuery(tx *gorm.DB) error { fmt.Println("BeforeQuery: Preparing to query user") return nil } // After query hook func (u *User) AfterQuery(tx *gorm.DB) error { fmt.Println("AfterQuery: User queried successfully") return nil }
Hook Execution Order
Create Operation
- BeforeCreate
- BeforeSave
- Execute INSERT
- AfterSave
- AfterCreate
Update Operation
- BeforeUpdate
- BeforeSave
- Execute UPDATE
- AfterSave
- AfterUpdate
Delete Operation
- BeforeDelete
- Execute DELETE
- AfterDelete
Query Operation
- BeforeQuery
- Execute SELECT
- AfterQuery / AfterFind
Practical Use Cases
1. Data Validation
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.Age < 0 { return errors.New("Age cannot be negative") } if !strings.Contains(u.Email, "@") { return errors.New("Invalid email format") } return nil }
2. Auto-generate Fields
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.ID == 0 { u.ID = generateUUID() } return nil }
3. Data Encryption
gofunc (u *User) BeforeSave(tx *gorm.DB) error { if u.Password != "" { u.Password = hashPassword(u.Password) } return nil }
4. Timestamp Management
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { now := time.Now() u.CreatedAt = now u.UpdatedAt = now return nil } func (u *User) BeforeUpdate(tx *gorm.DB) error { u.UpdatedAt = time.Now() return nil }
5. Audit Logging
gofunc (u *User) AfterCreate(tx *gorm.DB) error { log.Printf("User created: ID=%d, Name=%s", u.ID, u.Name) return nil } func (u *User) AfterUpdate(tx *gorm.DB) error { log.Printf("User updated: ID=%d, Name=%s", u.ID, u.Name) return nil } func (u *User) AfterDelete(tx *gorm.DB) error { log.Printf("User deleted: ID=%d", u.ID) return nil }
6. Soft Delete Handling
gofunc (u *User) BeforeDelete(tx *gorm.DB) error { // Update deletion time on soft delete if tx.Statement.Unscoped { // Real delete return nil } // Soft delete, update DeletedAt return nil }
Transaction Operations in Hooks
You can access transaction context in hooks:
gofunc (u *User) AfterCreate(tx *gorm.DB) error { // Create associated record in the same transaction profile := Profile{ UserID: u.ID, Bio: "New user", } return tx.Create(&profile).Error }
Skipping Hooks
Sometimes you need to skip hook execution:
go// Skip all hooks db.Session(&gorm.Session{SkipHooks: true}).Create(&user) // Use Unscoped to skip soft delete hooks db.Unscoped().Delete(&user)
Hook Return Errors
Returning an error from a hook will prevent the operation from continuing:
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.Name == "admin" { return errors.New("Creating admin user is not allowed") } return nil } // Usage err := db.Create(&user).Error if err != nil { fmt.Println("Create failed:", err) }
Notes
- Performance Impact: Hooks add operation overhead, avoid time-consuming operations in hooks
- Transaction Consistency: Operations in hooks are in the same transaction as the main operation, handle errors carefully
- Avoid Loops: Don't trigger operations that could cause infinite loops in hooks
- Error Handling: Hooks returning errors will prevent operations, ensure proper error handling
- Batch Operations: Hooks execute for each record in batch operations, pay attention to performance
- Test Coverage: Hook logic needs comprehensive unit test coverage
Best Practices
- Keep Simple: Hook logic should be simple and clear, avoid complex business logic
- Single Responsibility: Each hook should do only one thing
- Logging: Add appropriate logging in hooks
- Error Messages: Provide clear error messages for easier debugging
- Documentation: Add comments for complex hook logic