Persistence
Keel persistence is addon-based. The runtime does not ship a built-in database layer. Instead, applications compose official persistence addons and inject them into the modules that need them.
Today the official persistence integrations are:
| Addon | Backend | Implements | Extra capabilities |
|---|---|---|---|
ss-keel-gorm | PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle | contracts.Repository[T, ID, httpx.PageQuery, httpx.Page[T]] | GORM access, SQL engines, migration helpers, DB health checker |
ss-keel-mongo | MongoDB | contracts.Repository[T, ID, httpx.PageQuery, httpx.Page[T]] | Filter queries, direct collection access, custom ID conversion, Mongo health checker |
Official example coverage
Section titled “Official example coverage”The official examples currently cover:
ss-keel-examples/examples/08-gorm-postgresfor relational persistence withss-keel-gormss-keel-examples/examples/13-mongofor document persistence withss-keel-mongo
GORM integration from the official examples repo
Section titled “GORM integration from the official examples repo”ss-keel-examples/examples/08-gorm-postgres demonstrates the official relational addon wiring:
dbInstance, err := database.New(database.Config{ Engine: database.EnginePostgres, Host: dbHost, Port: dbPort, User: dbUser, Password: dbPassword, Database: dbName, SSLMode: dbSSLMode,})if err != nil { log.Error("failed to connect to database: %v", err)}
app.RegisterHealthChecker(database.NewHealthChecker(dbInstance))That example also shows:
db.AutoMigrate(...)- CRUD routes backed by GORM
/healthexposing database state
Repository wrappers generated by the CLI
Section titled “Repository wrappers generated by the CLI”When you want module-level repositories instead of calling the driver or ORM directly from handlers, the official CLI templates generate wrappers like these.
GORM template shape from keel:
type ProductEntity struct { database.EntityBase Name string `json:"name"`}
type ProductRepository struct { *database.GormRepository[ProductEntity, string] log *logger.Logger}
func NewProductRepository(log *logger.Logger, db *database.DBinstance) *ProductRepository { return &ProductRepository{ GormRepository: database.NewGormRepository[ProductEntity, string](db), log: log, }}Mongo template shape from keel:
The Mongo template generates a separate internal document type (ProductMongoDocument) that keeps the domain entity isolated from BSON details while preserving Keel’s string UUID IDs across every backend. Timestamps are stamped automatically via entity.OnCreate() / entity.OnUpdate() in each CRUD method.
type ProductMongoDocument struct { ID string `bson:"_id,omitempty"` CreatedAt int64 `bson:"created_at"` UpdatedAt int64 `bson:"updated_at"` Name string `bson:"name"`}
type ProductRepository struct { repo *mongo.MongoRepository[ProductMongoDocument, string] log *logger.Logger}
func NewProductRepository(log *logger.Logger, client *mongo.Client) *ProductRepository { return &ProductRepository{ repo: mongo.NewRepository[ProductMongoDocument, string](client, "product"), log: log, }}Those wrappers are generated via:
keel generate repository users/product --gormkeel generate repository users/product --mongoBoth addons also ship an EntityBase struct you can embed in any entity to get ID, CreatedAt, and UpdatedAt with the correct tags already set. See ss-keel-gorm and ss-keel-mongo for details.
Neutral multi-addon composition example
Section titled “Neutral multi-addon composition example”The following is a neutral example based on the official APIs. It shows how one service can compose both persistence addons without making either one part of the core runtime:
app := core.New(core.KConfig{ ... })
mongoClient, err := mongo.New(mongo.Config{ ... })if err != nil { appLogger.Error("failed to connect to mongodb: %v", err) return}app.RegisterHealthChecker(mongo.NewHealthChecker(mongoClient))app.Use(catalog.NewModule(appLogger, mongoClient))
sqlDB, err := database.New(database.Config{ Engine: database.EnginePostgres, DSN: config.GetEnvOrDefault("DATABASE_URL", ""), Logger: appLogger,})if err != nil { appLogger.Error("failed to connect to database: %v", err) return}app.RegisterHealthChecker(database.NewHealthChecker(sqlDB))app.Use(billing.NewModule(appLogger, sqlDB))This keeps the architecture clean:
- the core runtime owns composition
- addons own persistence implementations
- each module receives only the dependency it needs
Project structure
Section titled “Project structure”Projects created with keel new start with the CLI scaffold:
my-app/├── cmd/│ └── main.go├── internal/│ └── modules/│ └── starter/│ ├── module.go│ ├── controller.go│ ├── service.go│ └── dto.go├── go.mod├── keel.toml├── application.properties├── .env├── .env.example└── .gitignoreAs the application grows, persistence-enabled modules follow the same internal/modules/<name>/... structure, regardless of whether the repository is backed by GORM or Mongo.
CLI workflow for persistence
Section titled “CLI workflow for persistence”keel add gormkeel add mongo
keel generate repository users/product --gormkeel generate repository billing/audit-log --mongokeel add installs and wires the addon. keel generate repository uses the official repository template for the selected backend.
Choosing an addon
Section titled “Choosing an addon”- Use
ss-keel-gormfor relational models and SQL-first persistence. - Use
ss-keel-mongofor document-first persistence and Mongo-native query patterns. - Use both in the same service when different modules have different storage models.
See Architecture for the ecosystem layout and ss-keel-gorm / ss-keel-mongo for addon-specific details.