A type-safe generic base model pattern implementation for GORM, providing a clean and consistent interface for common database operations.
- Type-safe operations: Generic implementation ensures compile-time type safety
- Transaction support: Built-in transaction management with automatic rollback
- Flexible querying: Composable scope functions for building complex queries
- Pagination: Built-in pagination support with configurable page sizes
- Soft delete support: Full support for GORM's soft delete functionality
- Row locking: SELECT FOR UPDATE support for concurrent access control
- Batch operations: Efficient batch insert operations with configurable batch sizes
go get github.com/nullcache/gorm-pluspackage main
import (
"context"
"log"
"github.com/nullcache/gorm-plus"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Email string `gorm:"unique;not null"`
Age int `gorm:"default:0"`
}
func main() {
// Initialize database
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
// Auto migrate
db.AutoMigrate(&User{})
// Create base model
userBaseModel, err := gormplus.NewBaseModel[User](db)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Create a user
user := &User{Name: "John Doe", Email: "[email protected]", Age: 30}
err = userBaseModel.Create(ctx, nil, user)
if err != nil {
log.Fatal(err)
}
// Find user by email
foundUser, err := userBaseModel.First(ctx, gormplus.Where("email = ?", "[email protected]"))
if err != nil {
log.Fatal(err)
}
log.Printf("Found user: %+v", foundUser)
}The BaseModel[T] type is the main component that provides database operations for entities of type T.
// Create a new base model instance
userBaseModel, err := gormplus.NewBaseModel[User](db)Scopes are composable functions that modify GORM queries:
// Basic conditions
users, err := userBaseModel.List(ctx,
gormplus.Where("age > ?", 25),
gormplus.Order("name ASC"),
gormplus.Limit(10),
)
// Map-based conditions
users, err := userBaseModel.List(ctx,
gormplus.WhereEq(map[string]any{
"active": true,
"role": "admin",
}),
)Where(query, args...)- Add WHERE conditionsWhereEq(map[string]any)- Add equality conditions from mapOrder(string)- Add ORDER BY clauseSelect(columns...)- Select specific columnsLimit(int)- Limit number of resultsOffset(int)- Skip number of resultsWithDeleted()- Include soft-deleted recordsOnlyDeleted()- Only soft-deleted records
// Create
user := &User{Name: "Jane Doe", Email: "[email protected]"}
err := userBaseModel.Create(ctx, nil, user)
// Update
user.Age = 25
err = userBaseModel.Update(ctx, nil, user)
// Delete (soft delete if DeletedAt field exists)
err = userBaseModel.Delete(ctx, nil, gormplus.Where("id = ?", user.ID))// Find first record
user, err := userBaseModel.First(ctx, gormplus.Where("email = ?", "[email protected]"))
// List records
users, err := userBaseModel.List(ctx,
gormplus.Where("age > ?", 18),
gormplus.Order("name ASC"),
)
// Count records
count, err := userBaseModel.Count(ctx, gormplus.Where("active = ?", true))
// Check existence
exists, err := userBaseModel.Exists(ctx, gormplus.Where("email = ?", "[email protected]"))// Get paginated results
result, err := userBaseModel.Page(ctx, 1, 20, // page 1, 20 items per page
gormplus.Where("active = ?", true),
gormplus.Order("created_at DESC"),
)
// Access pagination info
fmt.Printf("Page: %d/%d, Total: %d, HasNext: %v",
result.Page,
(result.Total + int64(result.PageSize) - 1) / int64(result.PageSize),
result.Total,
result.HasNext,
)users := []*User{
{Name: "User 1", Email: "[email protected]"},
{Name: "User 2", Email: "[email protected]"},
{Name: "User 3", Email: "[email protected]"},
}
// Batch insert with default batch size (1000)
err := userBaseModel.BatchInsert(ctx, nil, users)
// Batch insert with custom batch size
err = userBaseModel.BatchInsert(ctx, nil, users, 100)// Manual transaction management
err := userBaseModel.Transact(ctx, func(ctx context.Context, tx *gorm.DB) error {
user1 := &User{Name: "User 1", Email: "[email protected]"}
if err := userBaseModel.Create(ctx, tx, user1); err != nil {
return err
}
user2 := &User{Name: "User 2", Email: "[email protected]"}
if err := userBaseModel.Create(ctx, tx, user2); err != nil {
return err
}
return nil // Commit transaction
})
// Row locking (requires transaction)
err = db.Transaction(func(tx *gorm.DB) error {
user, err := userBaseModel.FirstForUpdate(ctx, tx, gormplus.Where("id = ?", 1))
if err != nil {
return err
}
// Modify user safely
user.Balance += 100
return userBaseModel.Update(ctx, tx, &user)
})The library defines several standard errors:
// Check for specific errors
user, err := userBaseModel.First(ctx, gormplus.Where("id = ?", 999))
if errors.Is(err, gormplus.ErrNotFound) {
// Handle not found case
}
// Available errors:
// - gormplus.ErrInvalidType: Invalid generic type
// - gormplus.ErrNotFound: Record not found
// - gormplus.ErrTxRequired: Transaction required for operation
// - gormplus.ErrDangerous: Dangerous operation (e.g., delete without conditions)- Always use contexts: Pass context for cancellation and timeout support
- Handle transactions properly: Use the
Transactmethod for complex operations - Validate inputs: Check for required conditions before dangerous operations
- Use appropriate scopes: Combine scopes to build precise queries
- Handle errors: Always check for specific error types when needed
- Go 1.19 or higher
- GORM v1.25.0 or higher
MIT License. See LICENSE file for details.
Contributions are welcome. Please ensure all tests pass and follow the existing code style.