Database & ORM

Nimbus provides a full-featured ORM built on GORM, inspired by Laravel-style model workflows. Configure connections, use the query builder, models, migrations, relationships, pagination, transactions, hooks, serialization, and factories.

Documentation

Detailed guides for each topic:

Configuration

Set DB_DRIVER and DB_DSN in .env. Supported drivers: sqlite, postgres, mysql.

// bin/server.go
db, err := database.Connect(config.Database.Driver, config.Database.DSN)

// With debug (pretty-print SQL in development)
db, err := database.ConnectWithConfig(database.ConnectConfig{
    Driver: config.Database.Driver,
    DSN:    config.Database.DSN,
    Debug:  config.App.Env == "development",
})
DriverDSN example
sqlitedatabase.sqlite
postgreshost=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable
mysqluser:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True

Query builder

Use database.From(db, "posts") for raw table queries or db.Model(&Post{}) for model queries.

// Table query (returns plain objects)
var posts []map[string]any
database.From(database.Get(), "posts").
    Where("status", "published").
    OrderBy("created_at desc").
    Limit(10).
    Get(&posts)

// Model query (returns model instances)
var posts []Post
database.Get().Model(&Post{}).
    Where("status", "published").
    Order("created_at desc").
    Find(&posts)

Models

Embed database.Model for ID, CreatedAt, UpdatedAt, DeletedAt (soft delete), and optionally declare metadata with Table() and Fillable().

type Post struct {
    database.Model
    Title   string
    Content string
    Status  string
}

// Optional: override table name for Nimbus helpers.
func (Post) Table() string { return "posts" }

// Optional: control mass-assignable fields.
func (Post) Fillable() []string {
    return []string{"Title", "Content", "Status"}
}

CRUD

// Create
post := Post{Title: "Hello", Content: "World", Status: "draft"}
database.Get().Create(&post)

// Read
var p Post
database.Get().First(&p, id)
database.Get().Where("status", "published").Find(&posts)

// Update
database.Get().Model(&p).Updates(map[string]any{"title": "New Title"})

// Delete (soft delete when Model has DeletedAt)
database.Get().Delete(&p)

Pagination

database.Paginate returns a Lucid-style paginator with meta and URLs.

q := database.Get().Model(&Post{}).Where("status", "published").Order("created_at desc")
paginator, err := database.Paginate(q, &posts, page, 20)
paginator.BaseUrl("/posts")
// paginator.Data, paginator.Total, paginator.CurrentPage, paginator.LastPage
// paginator.FirstPageURL, paginator.NextPageURL, paginator.PrevPageURL

Transactions

Nimbus provides first-class support for database transactions via the nimbus.Transaction helper. This ensures data integrity by automatically committing if the function succeeds, or rolling back if an error occurs.

Managed Transactions

The following example demonstrates a bank transfer where money is debited from one account and credited to another. Both operations must succeed together.

func TransferFunds(fromID, toID uint, amount float64) error {
    return nimbus.Transaction(func(tx *nimbus.DB) error {
        // 1. Debit from source account
        if err := tx.Model(&Account{}).Where("id = ?", fromID).
            Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
            return err // Automatically rolls back
        }

        // 2. Credit to destination account
        if err := tx.Model(&Account{}).Where("id = ?", toID).
            Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
            return err // Automatically rolls back
        }

        return nil // Automatically commits
    })
}

Manual Transactions

If you need more control, you can use nimbus.Begin() to start a transaction manually.

tx := nimbus.Begin()

if err := tx.Create(&Account{Name: "Savings", Balance: 1000}).Error; err != nil {
    tx.Rollback()
    return err
}

tx.Commit()

Relationships & eager loading

Define relations with Nimbus tags (framework-agnostic) and use database.Preload or database.AutoPreload to avoid N+1.

type Post struct {
    database.Model
    UserID uint
    User   User ` + "`nimbus:\"belongsTo:User,foreignKey:UserID\"`" + `
}

type User struct {
    database.Model
    Posts []Post ` + "`nimbus:\"hasMany:Post,foreignKey:UserID\"`" + `
}

// Eager load
database.AutoPreload(database.Get(), &Post{}).Find(&posts)

Model hooks

Register lifecycle hooks with database.RegisterHooks.

database.RegisterHooks(database.Get(), "users", database.Hooks{
    BeforeCreate: func(db *gorm.DB) {
        // e.g. hash password
    },
    AfterCreate: func(db *gorm.DB) {
        // e.g. send welcome email
    },
})

Serialization

Exclude sensitive fields when returning JSON with database.Serialize.

m, _ := database.Serialize(user, database.SerializeOptions{
    Omit: []string{"password", "remember_token"},
})
return c.JSON(200, m)

Factories

Generate fake data for tests and seeders.

PostFactory := database.Define("posts", func(f *database.Faker) map[string]any {
    return map[string]any{
        "title":   f.Sentence(),
        "content": f.Paragraph(),
        "status":  "draft",
    }
})
PostFactory.Create(db)
PostFactory.CreateMany(db, 10)
PostFactory.Merge(map[string]any{"status": "published"}).Create(db)

Migrations

Use database.Migration (Name, Up, Down) and database/schema for schema changes. See Migrations.

Seeders

Implement database.Seeder or use database.SeedFunc. See Seeders.