Creating a Custom Crate
The Interface
Section titled “The Interface”Every storage backend implements Crate!T, which extends CrateAccess:
interface CrateAccess { @safe: IQuery get(); // Return a query over all items IQuery getItem(const string id); // Return a query for one item by ID Json addItem(const Json item); // Insert item, return it with generated ID Json updateItem(const Json item); // Replace item by ID, return updated void deleteItem(const string id); // Remove item by ID}
interface Crate(Type) : CrateAccess {}To create a custom crate, implement Crate!T for your model type and provide a matching IQuery implementation.
Minimal Example
Section titled “Minimal Example”Here’s a crate backed by a simple 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); }}The Query Side
Section titled “The Query Side”Your crate’s get() and getItem() methods must return an IQuery. This is the query builder interface that operations use for filtering, sorting, and pagination:
interface IQuery { @safe: IFieldQuery where(string field); // Start a field filter IQuery sort(string field, int order); // 1 = ascending, -1 = descending IQuery limit(size_t nr); // Limit result count IQuery skip(size_t nr); // Skip N results (pagination) InputRange!Json exec(); // Execute and return results size_t size(); // Count without fetching}where() returns IFieldQuery for building field conditions:
interface IFieldQuery { @safe: IFieldQuery equal(string value); IFieldQuery greaterThan(SysTime time); IFieldQuery lessThan(SysTime time); IFieldQuery like(string pattern); IFieldQuery arrayContains(string value); IFieldQuery anyOf(string[] values); IQuery and(); // Return to the parent query}For a simple crate, you can use MemoryQuery from crate.collection.memory — it implements IQuery over a Json[] array with in-memory filtering.
Using CrateConfig
Section titled “Using 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; }}The framework reads CrateConfig when generating routes — if config.deleteItem is false, no DELETE endpoint is created.
Registering Your Crate
Section titled “Registering Your Crate”Use your custom crate the same way you’d use MemoryCrate or MongoCrate:
auto crate = new HashMapCrate!User;auto router = new URLRouter();
router.crateSetup!RestApi .add(crate, authMiddleware);The framework only cares that your crate implements Crate!T. Everything else — route generation, middleware, serialization — works automatically.
Real-World Patterns
Section titled “Real-World Patterns”Connection Management
Section titled “Connection Management”For database-backed crates, handle connection setup in the constructor:
class RedisCrate(T) : Crate!T { private RedisClient client;
this(string host, ushort port = 6379) { this.client = connectRedis(host, port); }}ID Generation
Section titled “ID Generation”Crate expects items to have a string _id field after insertion. Your addItem must generate and assign an ID:
Json addItem(const Json item) @trusted { auto data = item.clone; data["_id"] = randomUUID().toString; // Or ObjectId, or auto-increment // ... store it ... return data;}Model Introspection
Section titled “Model Introspection”Use describeModel!T if you need compile-time information about the model’s fields:
class SmartCrate(T) : Crate!T { enum description = describeModel!T; enum idField = getIdField!description.name; // "_id" or custom
Json addItem(const Json item) @trusted { auto data = item.clone; data[idField] = generateId(); // ... }}This is how MemoryCrate and MongoCrate discover the ID field name at compile time.