Stop hand-writing SQL like it's 1999. This Go ORM gives your structs a life of their own with relationships, migrations, and zero drama.
- Type-safe: Fully leverages Go's type system with generics
- Zero drama: Simple, intuitive API with minimal boilerplate
- Relationships: Support for one-to-one, one-to-many, and many-to-many relationships
- Migrations: Easy schema migrations to evolve your database over time
- Multiple dialects: Support for SQLite, MySQL, and PostgreSQL
- Validation: Built-in validation using struct tags
- Transactions: First-class support for database transactions
- Hooks: Lifecycle hooks for entities (BeforeSave, AfterCreate, etc.)
- Query Builder: Fluent API for building complex queries
- Custom Queries: Support for raw SQL when needed
- No Code Generation: Uses reflection and generics instead of code generation
go get github.com/gooferOrm/goofer
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
"github.com/gooferOrm/goofer/dialect"
"github.com/gooferOrm/goofer/repository"
"github.com/gooferOrm/goofer/schema"
)
// User entity
type User struct {
ID uint `orm:"primaryKey;autoIncrement"`
Name string `orm:"type:varchar(255);notnull"`
Email string `orm:"unique;type:varchar(255);notnull"`
}
// TableName returns the table name for the User entity
func (User) TableName() string {
return "users"
}
func main() {
// Open SQLite database
db, err := sql.Open("sqlite3", "./db.db")
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Create dialect
sqliteDialect := dialect.NewSQLiteDialect()
// Register entity
if err := schema.Registry.RegisterEntity(User{}); err != nil {
log.Fatalf("Failed to register User entity: %v", err)
}
// Get entity metadata
userMeta, _ := schema.Registry.GetEntityMetadata(schema.GetEntityType(User{}))
// Create table
userSQL := sqliteDialect.CreateTableSQL(userMeta)
_, err = db.Exec(userSQL)
if err != nil {
log.Fatalf("Failed to create users table: %v", err)
}
// Create repository
userRepo := repository.NewRepository[User](db, sqliteDialect)
// Create a user
user := &User{
Name: "John Doe",
Email: "john@example.com",
}
// Save the user
if err := userRepo.Save(user); err != nil {
log.Fatalf("Failed to save user: %v", err)
}
fmt.Printf("Created user with ID: %d\n", user.ID)
// Find the user by ID
foundUser, err := userRepo.FindByID(user.ID)
if err != nil {
log.Fatalf("Failed to find user: %v", err)
}
fmt.Printf("Found user: %s (%s)\n", foundUser.Name, foundUser.Email)
}
The repository includes several examples demonstrating different features:
- Basic: Simple CRUD operations with SQLite
- MySQL: U`sing Goofer with MySQL
- PostgreSQL: Using Goofer with PostgreSQL
- Relationships: Working with one-to-one, one-to-many, and many-to-many relationships
- Migrations: Evolving your database schema over time
- Validation: Entity validation using struct tags and custom validation logic
- Hooks: Using lifecycle hooks for automated tasks
- Custom Queries: Advanced querying with raw SQL and aggregate functions
Check the examples
directory for complete code samples.
Entities are defined as Go structs with ORM tags:
type User struct {
ID uint `orm:"primaryKey;autoIncrement" validate:"required"`
Name string `orm:"type:varchar(255);notnull" validate:"required"`
Email string `orm:"unique;type:varchar(255);notnull" validate:"required,email"`
CreatedAt time.Time `orm:"type:timestamp;default:CURRENT_TIMESTAMP"`
Posts []Post `orm:"relation:OneToMany;foreignKey:UserID"`
}
func (User) TableName() string {
return "users"
}
primaryKey
: Marks the field as a primary keyautoIncrement
: Enables auto-incrementing for the primary keytype:<type>
: Specifies the database column typenotnull
: Makes the column not nullableunique
: Adds a unique constraintindex
: Creates an index on the columndefault:<value>
: Sets a default valuerelation:<type>
: Defines a relationship (OneToOne, OneToMany, ManyToOne, ManyToMany)foreignKey:<field>
: Specifies the foreign key fieldjoinTable:<table>
: Specifies the join table for many-to-many relationshipsreferenceKey:<field>
: Specifies the reference key for many-to-many relationships
The ORM integrates with the go-playground/validator
package for validation:
required
: Field cannot be emptyemail
: Must be a valid email addressmin=<number>
: Minimum value or lengthmax=<number>
: Maximum value or lengthgte=<number>
: Greater than or equal tolte=<number>
: Less than or equal tooneof=<value1 value2...>
: Must be one of the provided values
// Create a repository
repo := repository.NewRepository[User](db, dialect)
// Find by ID
user, err := repo.FindByID(1)
// Query builder
users, err := repo.Find().
Where("name LIKE ?", "%John%").
OrderBy("created_at DESC").
Limit(10).
Offset(0).
All()
// Count
count, err := repo.Find().Where("age > ?", 18).Count()
// Save (insert or update)
err := repo.Save(user)
// Delete
err := repo.Delete(user)
// Delete by ID
err := repo.DeleteByID(1)
// Transaction
err := repo.Transaction(func(txRepo *repository.Repository[User]) error {
// Operations within transaction
return nil
})
// Custom queries
var results []CustomStruct
rows, err := db.Query("SELECT * FROM users JOIN profiles ON users.id = profiles.user_id")
// Process rows...
Implement these interfaces on your entities to hook into the entity lifecycle:
// Before/After Create
func (u *User) BeforeCreate() error {
u.CreatedAt = time.Now()
return nil
}
func (u *User) AfterCreate() error {
fmt.Printf("User %s created at %v\n", u.Name, u.CreatedAt)
return nil
}
// Before/After Update
func (u *User) BeforeUpdate() error {
u.UpdatedAt = time.Now()
return nil
}
// Before/After Save (applies to both create and update)
func (u *User) BeforeSave() error {
// Normalize data before saving
u.Email = strings.ToLower(strings.TrimSpace(u.Email))
return nil
}
// Before/After Delete
func (u *User) BeforeDelete() error {
// Log or perform cleanup before deletion
return nil
}
Goofer provides a migration system to manage database schema changes:
// Create a migration manager
migrationManager := migration.NewManager(db, sqliteDialect)
// Register migrations
migrationManager.RegisterMigration("v1_to_v2", migrateV1ToV2)
// Define a migration function
func migrateV1ToV2(db *sql.DB, dialect dialect.Dialect) error {
// Add a new column
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s DEFAULT 0",
dialect.QuoteIdentifier("users"),
dialect.QuoteIdentifier("age"),
"INTEGER")
_, err := db.Exec(sql)
return err
}
// Run migrations
err := migrationManager.RunMigration("v1_to_v2")
Implement the Validate()
method on your entities for custom validation:
func (u *User) Validate() error {
// Create a validator
validator := validation.NewValidator()
// Validate using struct tags
errors, err := validator.ValidateEntity(u)
if err != nil {
return err
}
// Additional custom validation
if u.Role == "admin" && u.Age < 18 {
return fmt.Errorf("admin users must be at least 18 years old")
}
return nil
}
- Indexes: Add appropriate indexes for frequently queried columns
- Batch Operations: Use transactions for batch operations
- Eager Loading: Preload related entities to avoid N+1 query problems
- Connection Pooling: Configure connection pooling appropriately for your workload
- Raw SQL: For complex queries, use raw SQL instead of the query builder
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.