dusk-crux
v1.0.7
Published
An API mock library built right into NPX to create self-contained, plug and play API routing.
Maintainers
Readme

Dusk Crux
Dusk Crux bootstraps a file-backed mock API server. Generate a .crux workspace and start the watcher-driven server without leaving your project root.
Quickstart
npx -y dusk-crux
npm install
npm run dusk-cruxnpx -y dusk-cruxscaffolds the.cruxdirectory and adds adusk-cruxnpm script if missing.npm run dusk-cruxproxies todusk-crux run, starting the mock server on port 4000 by default.- Pass
--portor--rootwhen needed, for examplenpm run dusk-crux -- --port 4100.
Core Concepts
File-backed routing
- Every folder under
.cruxmaps directly to an HTTP route. Static segments reuse the folder name, while dynamic segments use bracket notation (users/[id]→/users/:id). The JSON file that sits in the last folder defines the route contract (for example,.crux/users/[id]/users.id.crux.json). - The file watcher (chokidar) keeps the Express router in sync with the filesystem. Saving or removing a route file hot-swaps the in-memory router without restarting the process.
Example route file (.crux/users/[id]/users.id.crux.json):
{
"actions": [
{
"name": "user_id_json",
"req": {
"method": "get",
"params": { "id": 1 },
"query": { "date_created": "00010101" },
"headers": { "accept": "application/json" }
},
"res": {
"status": 200,
"bodyFile": "test_body.json"
}
},
{
"name": "user_id_xml",
"req": {
"method": "get",
"params": { "id": 1 },
"query": { "date_created": "00010101" },
"headers": { "accept": "application/xml" }
},
"res": {
"status": 200,
"bodyFile": "test_body.xml"
}
}
]
}Defaults, routes, and actions
defaults.jsonsets defaults for requests and responses at any level of the.cruxtree: shared headers, default status codes, or reusable query parameters. The rootdefaults.jsonapplies to every route; adefaults.jsoninside any subfolder applies only to routes beneath that folder.- Defaults cascade from the root down. Each
defaults.jsondeep-merges on top of the accumulated ancestor defaults; a child file overrides only the fields it expressly declares. Each route file's ownglobalsblock then merges on top of the fully-cascaded defaults, and finally every action'sreq/resmerges on top of that. - Deep-merge semantics: object values merge key-by-key, arrays replace wholesale, and a child writing
"field": nullerases the inherited value. defaults.jsonfiles never declareactions— they only contributereq/resdefaults. They are not passed through route validation.- Action matching is deterministic. Crux normalizes method names, then picks the first action whose
req.method,req.params,req.headersandreq.queryall match the incoming request. - Missing methods yield a 405 with an
Allowheader. Missing matches yield a structured 400 explaining the mismatch.
Example root defaults (.crux/defaults.json):
{
"req": {
"method": "get",
"headers": {
"policy": "warn",
"schema": {
"authorization": {
"scheme": "Bearer",
"pattern": "^[A-Za-z0-9._-]{20,}$"
},
"content-type": "application/json"
}
}
},
"res": {
"status": 200
}
}Example cascading defaults — relax CORS for just the /v1/public/** subtree while every other route keeps the root defaults:
.crux/
defaults.json # root defaults (strict auth, no CORS)
v1/
public/
defaults.json # adds CORS headers for this subtree
health/
health.crux.json
version/
version.crux.json
internal/
users/
users.crux.json # inherits root defaults only.crux/v1/public/defaults.json:
{
"res": {
"headers": {
"access-control-allow-origin": "*",
"access-control-allow-methods": "GET, OPTIONS"
}
}
}Response composition
- Response shapes come from the merged
resobject: choose status codes, headers, and optionalbodyFile. ThebodyFilepath must stay inside the route directory, keeping fixtures self-contained. - When
bodyFileis present, Crux streams the referenced file back with an inferredContent-Type. JSON, XML, HTML, text, and binary payloads are supported out of the box. - Validation runs before a route is registered. Invalid configs are skipped and logged so broken fixtures never take over a path.
Fixtures referenced by the example route:
[
{
"name": "Count Olaf",
"type": "Character",
"famous_quote": "WRONG! It's a list."
}
]<user id="1">
<name>Count Olaf</name>
<type>Character</type>
<famous_quote>WRONG! It's a list.</famous_quote>
</user>CORS preflight
- Every route automatically answers
OPTIONSwith204 No Contentso browser preflight requests can pass. The response includes the mergedres.headersfrom every cascadeddefaults.jsonand the route file, so CORS headers declared in defaults (e.g.access-control-allow-origin,access-control-allow-methods,access-control-allow-headers) are echoed back on the preflight. - The auto-registered preflight also sets an
Allowheader listing every method declared on the route, plusOPTIONS. - If a route defines its own
OPTIONSaction, that action wins and the auto-preflight is skipped.
Operator utilities
- The root route (
GET /) lists every discovered route, its dynamic params, and the available actions. Use it to confirm wiring before invoking real clients. GET /healthruns the validator across all loaded configs and returns warnings or errors as machine-readable diagnostics.
CLI surface
dusk-crux initscaffolds.crux, adds thedusk-cruxscript, and pins the package as a devDependency to the generated project when it runs insidenpx.dusk-crux runstarts the watcher-driven Express server. Override the port with--portand relocate the config tree with--rootwhen your mock data lives elsewhere.- The CLI never mutates files outside the working tree, so you can check in the scaffold and iterate alongside the rest of your app.
Development
npm install
npm run build
npm run startnpm run start mirrors npm run dusk-crux and launches the server against the local .crux fixtures for package development.
