Skip to content

Den

Go gophers organizing documents in their den

"Every burrow needs a den — a place to store what matters and find it again when you need it."

An ODM for Go with two storage backends — SQLite and PostgreSQL. Same API, your choice of engine.

Features

  • Two backends, one API — SQLite (embedded, pure Go, no CGO) and PostgreSQL (server-based, JSONB + GIN indexes)
  • Chainable QuerySet — fluent builder with lazy evaluation: Where, Sort, Limit, Skip, All, First, Count
  • Range iterationIter() returns iter.Seq2[*T, error] for memory-efficient streaming
  • Typed relationsLink[T] and []Link[T] with cascade write/delete and eager/lazy fetch
  • Full-text search — FTS5 (SQLite), tsvector (PostgreSQL), unified Search() API
  • Lifecycle hooksBeforeInsert, AfterUpdate, Validate, and more via interfaces
  • Change tracking — opt-in Tracked with IsChanged, GetChanges, Revert
  • Soft delete — embed SoftDelete, automatic query filtering, HardDelete for permanent removal
  • Attachments & storage — embed Attachment, install a den.Storage backend, hard-delete cascades to byte cleanup
  • Optimistic concurrency — revision-based conflict detection
  • TransactionsRunInTransaction with panic-safe rollback
  • Migrations — registry-based, each migration runs atomically
  • Struct tag validation — optional validate tags via go-playground/validator

Quick Example

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/oliverandrich/den"
    _ "github.com/oliverandrich/den/backend/sqlite"
    "github.com/oliverandrich/den/document"
    "github.com/oliverandrich/den/where"
)

type Product struct {
    document.Base
    Name  string  `json:"name"  den:"index"`
    Price float64 `json:"price" den:"index"`
}

func main() {
    ctx := context.Background()

    db, err := den.OpenURL(ctx, "sqlite:///products.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    den.Register(ctx, db, &Product{})
    den.Save(ctx, db, &Product{Name: "Widget", Price: 9.99})

    products, _ := den.NewQuery[Product](db,
        where.Field("price").Lt(20.0),
    ).Sort("name", den.Asc).All(ctx)

    for _, p := range products {
        fmt.Printf("%s — $%.2f\n", p.Name, p.Price)
    }
}