Database Models
Radish generates Mongoose models for each entity defined in your blueprints.
Overview
Models provide the database layer abstraction, handling:
- Schema definitions
- Indexes
- Timestamps (createdAt, updatedAt)
- Owner scoping
- Versioning hooks (if enabled)
Generated Models
Location: .radish/lib/datalayer/core/models/
// .radish/lib/datalayer/core/models/product.model.ts
export const ProductModel = mongoose.model('Product', productSchema);
Features
Default Collation
All models are created with a default collation for case-insensitive, natural number sorting:
{ locale: 'en', strength: 2, numericOrdering: true }
This means string comparisons and sorting are case-insensitive by default, and numeric strings sort naturally (1, 2, 10 instead of 1, 10, 2). You can override this per-entity in your blueprint with a custom collation object.
Automatic Indexes
Models include automatic indexes on:
- Reference fields (
reffields withobjectIdtype) - Fields listed in the entity's
filtersarray - Compound indexes defined in the entity's
indexesarray ownerId(always indexed)archivedAt(always indexed)
Note: Setting
index: trueon a field in your blueprint does NOT create an actual MongoDB index. Onlyreffields,filtersentries, and explicitindexesdefinitions produce indexes. Use theindexesarray for fields that need indexing beyond what is auto-generated.
Timestamps
All models include createdAt and updatedAt timestamps.
Owner Scoping
Models support owner-based access control when configured in blueprints.
Versioning
When versioning is enabled, models include hooks to track changes.
Relations
Forward references
A field with a ref becomes a Mongoose reference, populatable by name:
"Lesson": { "fields": { "courseId": { "type": "objectId", "ref": "Course" } } }
await lessonService.get(id, { paths: ['courseId'] }); // resolves the Course
Inverse relations (reverse populate)
The reverse direction is generated automatically. Course gets a lessons accessor
without declaring anything:
await courseService.get(id, { paths: ['lessons'] }); // resolves its Lessons
Emitted as a Mongoose virtual, declared before the model is created:
CourseSchema.virtual("lessons", {
ref: "Lesson", localField: "_id", foreignField: "courseId", justOne: false
});
Everything is derived — there is no blueprint key.
| from | |
|---|---|
| where the accessor goes | the ref target |
| its name | the referencing entity's plural, camelCased to a valid identifier |
| one or many | whether the referencing field carries a unique index |
A unique index means one record can point back, so the accessor is a single document and its name is singularised:
"Profile": { "fields": { "userId": { "type": "objectId", "ref": "User", "unique": true } } }
await userService.get(id, { paths: ['profile'] }); // one Profile, not an array
Without the unique index the same relation yields User.profiles as an array. Cardinality
follows the index, so adding or removing one changes the accessor — worth knowing if a
name changes unexpectedly after a schema edit.
Which entities get them
All of them. Builtin (User.apiKeys, User.grants), business, system-owned
(ScheduledJob.jobExecutions), feature-synthesised (DatasetDefinition.datasetFields),
and entities extending a builtin — where refs inherited from the parent count, not just
the entity's own fields.
An entity nothing references simply gets none.
Collisions
An accessor that would collide with a real field on the target, or with another inverse claiming the same name, is skipped with a warning naming both sides. It is not silently dropped, because an inverse that exists on one model and not another for invisible reasons is worse than one that is missing loudly.
Two things worth knowing
Virtuals are lazy. An inverse costs nothing until something populates it. Declaring many is not itself a performance concern — populating a large one on a hot path is.
.lean() drops virtuals. The repo layer does not use it, so this does not arise
today. Any code path that adds it will find every inverse silently undefined — no error,
just an absent field.
Related Documentation
- Repositories - Data access layer built on models
- Contracts - Type definitions
- Versioning - Version tracking features