Skip to content

Models & CRUD

A Crate model is a D struct that follows a few conventions:

Every model must have a string _id field. This is the primary key used for item-level operations:

struct User {
string _id;
string name;
string email;
}

Use the @optional attribute (from vibe.d serialization) to mark fields that don’t need to be provided on creation:

struct Article {
string _id;
string title;
string body;
@optional string category = "";
@optional int viewCount = 0;
}

Optional fields are excluded from serialization when they have their default value, and they don’t need to be included in POST requests.

Models can contain nested structs:

struct Point {
string type = "Point";
float[2] coordinates;
}
struct Site {
string _id;
string name;
Point position;
}

When serialized, nested structs appear inline in the JSON:

{
"site": {
"_id": "000000000000000000000001",
"name": "Central Park",
"position": {
"type": "Point",
"coordinates": [40.785091, -73.968285]
}
}
}

When a model references another model (a struct with its own _id), it’s stored as an ID string. You must register a crateGetter for the referenced model:

struct Team {
string _id;
string name;
}
struct Campaign {
string _id;
string title;
Team team; // Reference to a Team
}
auto teamCrate = new MongoCrate!Team("teams");
auto campaignCrate = new MongoCrate!Campaign("campaigns");
crateGetters["Team"] = &teamCrate.getItem;
crateRouter.add(teamCrate);
crateRouter.add(campaignCrate);

In POST/PATCH requests, references are sent as ID strings:

{
"campaign": {
"title": "Save the park",
"team": "000000000000000000000001"
}
}

Without the crateGetter registration, POST and PATCH requests to the Campaign endpoint will fail with a 500 error.

When you call add() or prepare(), Crate generates six endpoints:

Returns all items as a JSON array:

{
"users": [
{"_id": "...", "name": "Alice", "email": "alice@example.com"},
{"_id": "...", "name": "Bob", "email": "bob@example.com"}
]
}

Returns a single item:

{
"user": {
"_id": "000000000000000000000001",
"name": "Alice",
"email": "alice@example.com"
}
}

Returns 404 if the item doesn’t exist.

Creates a new item. The request body wraps the data in a key matching the model name:

{
"user": {
"name": "Charlie",
"email": "charlie@example.com"
}
}

The response includes the generated _id.

Replaces an entire item. All non-optional fields must be provided:

{
"user": {
"name": "Alice Updated",
"email": "newalice@example.com"
}
}

Partially updates an item. Only include the fields you want to change:

{
"user": {
"email": "newemail@example.com"
}
}

Unspecified fields remain unchanged.

Deletes an item. Returns 204 on success, 404 if the item doesn’t exist.

For fine-grained control over JSON representation, implement toJson and fromJson on your struct:

struct Site {
string _id;
Point position;
Json toJson() const @safe {
Json data = Json.emptyObject;
data["_id"] = _id;
data["position"] = position.serializeToJson;
return data;
}
static Site fromJson(Json src) @safe {
Site site;
site._id = src["_id"].get!string;
// custom deserialization logic
return site;
}
}

Crate provides several collection backends:

CollectionUse Case
MemoryCrate!TTesting and prototyping. Data lives in memory.
MongoCrate!TProduction MongoDB storage.
CacheCrate!TWraps another crate with an in-memory cache.
ProxyCrate!TDelegates to another crate, useful for decoration.

All collections implement the same Crate!T interface, so switching backends requires changing only the construction:

// Development
auto products = new MemoryCrate!Product;
// Production
auto products = new MongoCrate!Product("products");