Skip to content

Creating a Custom Crate

Implement Crate!T and provide a matching IQuery implementation. The framework handles everything else — routing, middleware, serialization.

A crate backed by an associative array:

import crate.base;
import vibe.data.json;
class HashMapCrate(T) : Crate!T {
private {
Json[string] store;
ulong nextId;
}
IQuery get() @trusted {
return new ArrayQuery(store.values);
}
IQuery getItem(const string id) @trusted {
if (id in store)
return new ArrayQuery([store[id]]);
return new ArrayQuery([]);
}
Json addItem(const Json item) @trusted {
auto data = item.clone;
auto id = (++nextId).to!string;
data["_id"] = id;
store[id] = data;
return data;
}
Json updateItem(const Json item) @trusted {
auto id = item["_id"].get!string;
store[id] = item.clone;
return store[id];
}
void deleteItem(const string id) @trusted {
store.remove(id);
}
}

Your get() and getItem() must return an IQuery. For simple backends, use MemoryQuery from crate.collection.memory:

import crate.collection.memory : MemoryQuery;
IQuery get() @trusted {
return new MemoryQuery(store.values);
}

This gives you filtering, sorting, pagination, and projections for free.

For a database-backed crate, implement IQuery to translate calls into native queries:

class RedisQuery : IQuery {
@safe:
IFieldQuery where(string field) { /* Build Redis query */ }
IQuery sort(string field, int order) { /* SORT command */ }
IQuery limit(size_t nr) { /* LIMIT */ }
IQuery skip(size_t nr) { /* OFFSET */ }
InputRange!Json exec() { /* Execute and stream results */ }
size_t size() { /* Count */ }
}

Accept an optional CrateConfig!T in your constructor to let users disable specific operations:

class HashMapCrate(T) : Crate!T {
private CrateConfig!T _config;
this(CrateConfig!T config = CrateConfig!T()) {
_config = config;
}
}

When config.deleteItem is false, no DELETE endpoint is generated.

Your addItem must assign a string _id before returning:

Json addItem(const Json item) @trusted {
auto data = item.clone;
data["_id"] = randomUUID().toString; // Or ObjectId, auto-increment, etc.
// ... store it ...
return data;
}

Use describeModel!T for compile-time information about the model’s fields:

class SmartCrate(T) : Crate!T {
enum description = describeModel!T;
enum idField = getIdField!description.name;
Json addItem(const Json item) @trusted {
auto data = item.clone;
data[idField] = generateId();
// ...
}
}

Register it the same way as any built-in crate:

auto crate = new HashMapCrate!User;
router.crateSetup!RestApi.add(crate, authMiddleware);