Middleware
What is Middleware?
Section titled “What is 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.
UDA Annotations
Section titled “UDA Annotations”Each middleware method is annotated with one or more UDAs that control when it runs:
| UDA | Applies To |
|---|---|
@any | All operations |
@get | Both GET list and GET item |
@getList | GET list only |
@getItem | GET item only |
@create | POST create |
@patch | PATCH update |
@replace | PUT replace |
@put | PUT replace (alias for @replace) |
@update | Both PATCH and PUT |
@delete_ | DELETE |
@mapper | Response mapping (transforms JSON output) |
Writing a Query Filter
Section titled “Writing a Query Filter”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".
Query Filter with Typed Parameters
Section titled “Query Filter with Typed Parameters”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.
Writing a Request Middleware
Section titled “Writing a Request Middleware”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 } }}Operation-Specific Middleware
Section titled “Operation-Specific Middleware”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"); } }}Writing a Response Mapper
Section titled “Writing a Response Mapper”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.
Chaining Middleware
Section titled “Chaining Middleware”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
Middleware with add()
Section titled “Middleware with add()”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.
Next Steps
Section titled “Next Steps”- Create custom operations for non-CRUD endpoints
- Set up authentication with OAuth2