Custom Operations
Action Methods
Section titled “Action Methods”The simplest way to add custom endpoints is to define methods directly on your model struct. Crate calls these “actions” and auto-discovers them when you use prepare() or add().
GET Actions (No Parameters)
Section titled “GET Actions (No Parameters)”A method that returns a value becomes a GET endpoint:
struct User { string _id; string firstName; string lastName;
string getFullName() { return firstName ~ " " ~ lastName; }}This creates GET /users/:id/getFullName which returns the user’s full name as a string.
POST Actions (With Parameters)
Section titled “POST Actions (With Parameters)”A method that takes a parameter becomes a POST endpoint:
struct User { string _id; string name;
string setName(string newName) { name = newName; return name; }}This creates POST /users/:id/setName. The request body is the parameter value.
Actions with HTTP Request Access
Section titled “Actions with HTTP Request Access”An action can also accept an HTTPServerRequest for full request access:
import vibe.http.server : HTTPServerRequest;
struct Report { string _id; string data;
string handleRequest(HTTPServerRequest request) { // Access headers, query params, etc. return "processed"; }}Manual Action Exposure
Section titled “Manual Action Exposure”By default, prepare() and add() auto-discover and expose all action methods on a model. If you want to control which actions are exposed, use using() and manually call .expose!:
auto typeRouter = crateRouter.using(userCrate);
// Only expose specific actionstypeRouter.expose!"getFullName";typeRouter.expose!"setName";// handleRequest is NOT exposedItem Operations
Section titled “Item Operations”For more control, use .itemOperation!() to define custom endpoints with explicit handlers:
auto typeRouter = crateRouter.prepare(reportCrate);
// Define a custom GET operation@mime("custom/mime")string generatePdf() { return "pdf-data";}typeRouter.itemOperation!("generate-pdf")(&generatePdf);This creates GET /reports/:id/generate-pdf.
For POST operations, the handler takes a parameter:
@mime("application/json")int calculate(int value) { return value * 2;}typeRouter.itemOperation!("calculate")(&calculate);This creates POST /reports/:id/calculate.
Fully Custom Operations
Section titled “Fully Custom Operations”For endpoints that don’t follow the item pattern, use .withCustomOperation() with a class that extends ApiOperation:
import crate.http.operations;
class HealthCheckOperation : ApiOperation { this() { CrateRule rule; rule.request.path = "/health"; rule.request.method = HTTPMethod.GET; rule.response.statusCode = 200; super(rule); }
override void handle(OperationContext ctx) { ctx.response.writeJsonBody(Json(["status": Json("ok")])); }}Register it on a router:
crateRouter .prepare(someCrate) .withCustomOperation(new HealthCheckOperation);Or on a blank router (no model attached):
crateRouter .blank() .withCustomOperation(new HealthCheckOperation);Real-World Example: History Operation
Section titled “Real-World Example: History Operation”A common pattern is adding a history/audit endpoint to a model:
class HistoryOperation : ApiOperation { this(string modelPath) { CrateRule rule; rule.request.path = modelPath ~ "/:id/history"; rule.request.method = HTTPMethod.GET; rule.response.statusCode = 200; super(rule); }
override void handle(OperationContext ctx) { auto id = ctx.request.params["id"]; // Fetch history from audit log... ctx.response.writeJsonBody(historyData); }}
crateRouter .prepare(campaignCrate) .withCustomOperation(new HistoryOperation("/campaigns"));Next Steps
Section titled “Next Steps”- Set up authentication with OAuth2
- Learn about multi-protocol support for MCP, GraphQL, and JSON:API