Skip to content

Middleware

Middleware in Crate are classes that intercept requests at various points in the operation pipeline. They can:

  • Filter queries — restrict which items are returned based on request parameters
  • Validate data — reject invalid create/update requests
  • Map responses — transform how items appear in the response
  • Block requests — enforce authentication or authorization

Middleware classes use User-Defined Attributes (UDAs) to declare which operations they apply to.

Each middleware method is annotated with one or more UDAs that control when it runs:

UDAApplies To
@anyAll operations
@getBoth GET list and GET item
@getListGET list only
@getItemGET item only
@createPOST create
@patchPATCH update
@replacePUT replace
@putPUT replace (alias for @replace)
@updateBoth PATCH and PUT
@delete_DELETE
@mapperResponse mapping (transforms JSON output)

A query filter modifies the database query based on request parameters. It receives an IQuery selector and returns a modified one:

class TypeFilter {
@any
IQuery any(IQuery selector, HTTPServerRequest request) @safe {
if ("type" !in request.query) {
return selector;
}
selector.where("position.type").equal(request.query["type"]);
return selector;
}
}

Use it:

crateRouter
.prepare(siteCrate)
.and(new TypeFilter);

Now GET /sites?type=Point returns only sites with position.type == "Point".

Instead of manually parsing query strings, define a QueryParams struct inside your filter class:

class PaginationFilter {
struct QueryParams {
int page = 1;
int perPage = 20;
}
@getList
IQuery getList(IQuery selector, QueryParams params) @safe {
selector.skip((params.page - 1) * params.perPage);
selector.limit(params.perPage);
return selector;
}
}

Crate automatically parses query string parameters into your QueryParams struct.

Request middleware intercepts the HTTP request before the operation runs. It can modify the request, block it, or set response headers:

class AuthMiddleware {
@any
void any(HTTPServerRequest req, HTTPServerResponse res) @safe {
if ("Authorization" !in req.headers) {
res.statusCode = 401;
res.writeBody("Unauthorized");
return; // Stops the pipeline
}
}
}

Apply different logic to different operations:

class AccessControl {
@getList @getItem
void readAccess(HTTPServerRequest req, HTTPServerResponse res) @safe {
// Public read access - no check needed
}
@create @replace @patch @delete_
void writeAccess(HTTPServerRequest req, HTTPServerResponse res) @safe {
if (!isAdmin(req)) {
res.statusCode = 403;
res.writeBody("Admin access required");
}
}
}

A mapper transforms JSON items before they’re sent in the response. Use the @mapper UDA:

class UserMapper {
@mapper
Json mapper(HTTPServerRequest req, const Json item, ) @trusted nothrow {
try {
Json result = item.clone;
// Remove sensitive fields from the response
result.remove("passwordHash");
result.remove("internalNotes");
return result;
} catch (Exception) {
return item;
}
}
}

Mappers run on each item in the response, both for single-item and list responses.

Use .and() to chain multiple middleware on a route:

crateRouter
.prepare(userCrate)
.and(authMiddleware)
.and(adminCheck)
.and(validationFilter)
.and(userMapper)
.and(paginationFilter);

Middleware runs in the order they’re added. This matters because:

  • Authentication should run first (reject unauthorized requests early)
  • Query filters should run before pagination
  • Mappers run on the response after the operation completes

For simpler models, pass middleware directly to add() as arguments:

crateRouter.add(
productCrate,
authMiddleware,
validationFilter,
paginationFilter
);

This is equivalent to calling prepare() followed by .and() for each middleware.