Creating a Custom Crate
Implement Crate!T and provide a matching IQuery implementation. The framework handles everything else — routing, middleware, serialization.
Minimal Example
Section titled “Minimal Example”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); }
Json[] addItems(const Json[] items) @trusted { Json[] result; foreach (item; items) { result ~= addItem(item); } return result; }
void deleteItems(const string[] ids) @trusted { foreach (id; ids) { deleteItem(id); } }}addItems and deleteItems are part of CrateAccess, so every crate must supply them. Looping over the single-item methods, as above, is a correct implementation — override it only when your backend has a bulk primitive worth using. MongoCrate batches into insertMany and deleteMany calls of up to 2000 documents.
The Query Side
Section titled “The Query Side”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 */ }}CrateConfig
Section titled “CrateConfig”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.
ID Generation
Section titled “ID Generation”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;}Model Introspection
Section titled “Model Introspection”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(); // ... }}Using Your Crate
Section titled “Using Your Crate”Register it the same way as any built-in crate:
auto crate = new HashMapCrate!User;router.crateSetup!RestApi.add(crate, authMiddleware);