blendwerk
[ˈblɛntvɛrk] — German: illusion, deceptive appearance.
A file-based mock HTTP/HTTPS server where your directory structure IS the API. No databases, no config files — just directories and text files.
Directory = API
Your directory structure IS your API. Folders become URL paths, filenames become HTTP methods. No config files, no databases.
Ready in Seconds
Create a file, run blendwerk, done. Hot-reload lets you incrementally build your mocks as you develop and test.
SSL Out of the Box
Self-signed certificates generated automatically. Test your HTTPS clients without certificate hassle. Custom certs supported too.
Quick Start
cargo install blendwerkAvailable on crates.io
Download pre-built binaries from GitHub Releases:
View Releasesgit clone https://github.com/jakobwesthoff/blendwerk.git
cd blendwerk
cargo build --releaseBasic Usage
# Create a mock
mkdir -p mocks/api/users
cat > mocks/api/users/GET.json << 'EOF'
---
status: 200
---
{"users": [{"id": 1, "name": "Alice"}]}
EOF
# Run
blendwerk ./mocks
# Test
curl http://localhost:8080/api/usersDocumentation
Point blendwerk at a directory and it serves mock HTTP/HTTPS responses based on the file structure. No configuration needed — just files and folders.
blendwerk <DIRECTORY> [OPTIONS]Core Concepts
Directory Structure Maps to Routes
Your directory structure IS your API. Folders become URL paths, filenames become HTTP methods. That's it.
mocks/
├── api/
│ ├── users/
│ │ ├── GET.json # GET /api/users
│ │ ├── POST.json # POST /api/users
│ │ └── [id]/ # Path parameter
│ │ ├── GET.json # GET /api/users/:id
│ │ ├── PUT.json # PUT /api/users/:id
│ │ └── DELETE.json # DELETE /api/users/:id
│ └── health/
│ └── GET.json # GET /api/health
└── GET.html # GET /Rules:
- Method names are case-insensitive (
GET.json,get.json,Get.jsonall work) - Use
[paramName]directories for path parameters (matches any path segment) - Hot-reload: changes to files are detected automatically
Route Matching: Routes use first-match-wins ordering. Both static routes and [param] routes are matched in discovery order.
Error Responses:
404 Not Found— No route matches the path405 Method Not Allowed— Path exists but method isn't defined
Query Parameters: Query strings don't affect route matching — all requests to a path use the same mock regardless of query parameters. However, query parameters are captured in request logs.
Response Files
Format
Response files use optional YAML frontmatter followed by the response body:
---
status: 201
headers:
X-Request-Id: abc-123
Cache-Control: no-cache
delay: 100
---
{"created": true, "id": 42}Frontmatter Fields
| Field | Type | Default | Description |
|---|---|---|---|
status | integer | 200 | HTTP status code |
headers | map | {} | Response headers |
delay | integer | 0 | Delay in milliseconds before responding |
All fields are optional. Files without frontmatter return status 200.
Content-Type
Automatically inferred from file extension (can of course be overridden in headers):
.json→application/json.html→text/html.xml→application/xml.txt→text/plain
Examples
Error response:
# mocks/api/protected/GET.json
---
status: 401
headers:
WWW-Authenticate: Bearer realm="api"
---
{"error": "unauthorized"}Simulating latency:
# mocks/api/slow/GET.json
---
delay: 2000
---
{"message": "This took 2 seconds"}Multiple methods:
# mocks/api/items/GET.json
{"items": []}
# mocks/api/items/POST.json
---
status: 201
---
{"created": true}Configuration
Command Line Options
Usage: blendwerk [OPTIONS] <DIRECTORY>
Arguments:
<DIRECTORY>
Directory containing mock responses
Options:
-p, --http-port <HTTP_PORT>
HTTP port
[default: 8080]
-s, --https-port <HTTPS_PORT>
HTTPS port
[default: 8443]
--http-only
Only serve HTTP (no HTTPS)
--https-only
Only serve HTTPS (no HTTP)
--cert-mode <CERT_MODE>
Certificate mode
Possible values:
- none: No HTTPS, HTTP only
- self-signed: Generate self-signed certificate on startup
- custom: Use custom certificate files
[default: self-signed]
--cert-file <CERT_FILE>
Path to certificate file (required for custom cert mode)
--key-file <KEY_FILE>
Path to private key file (required for custom cert mode)
--request-log <REQUEST_LOG>
Directory to log all incoming requests
--request-log-format <REQUEST_LOG_FORMAT>
Format for request logs
[default: json]
[possible values: json, yaml]
-h, --help
Print help
-V, --version
Print versionHTTP/HTTPS Modes
Default (HTTP + HTTPS with self-signed cert):
blendwerk ./mocks
# HTTP on :8080, HTTPS on :8443HTTP only:
blendwerk ./mocks --http-only
# or
blendwerk ./mocks --cert-mode noneHTTPS only:
blendwerk ./mocks --https-onlyCustom certificate:
blendwerk ./mocks --cert-mode custom --cert-file server.crt --key-file server.keyRequest Logging
blendwerk can log all incoming requests to a directory structure that mirrors your API routes. This is useful for debugging, testing, and understanding how your mock API is being used.
Enable request logging:
blendwerk ./mocks --request-log ./request-logsDirectory structure:
request-logs/
├── api/
│ └── users/
│ ├── GET/
│ │ ├── 2025-01-28T15-30-45.123456Z_01HQKP6J9Z0000000000000000.json
│ │ └── 2025-01-28T15-31-12.456789Z_01HQKP7A1A0000000000000000.json
│ └── POST/
│ └── 2025-01-28T15-32-00.789012Z_01HQKP8B2B0000000000000000.jsonLog file format:
Each request is logged as a separate file containing complete request and response information:
{
"metadata": {
"timestamp": "2025-01-28T15-30-45.123456Z",
"request_id": "01HQKP6J9Z0000000000000000"
},
"request": {
"method": "GET",
"uri": "/api/users?page=2",
"path": "/api/users",
"query": "page=2",
"headers": {
"user-agent": "curl/8.0.0",
"accept": "*/*"
},
"body": null,
"matched_route": "/api/users"
},
"response": {
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": "{\"users\": [...]}",
"delay_ms": 0
}
}YAML format:
blendwerk ./mocks --request-log ./request-logs --request-log-format yamlFilenames use ISO 8601 timestamps plus ULIDs for sortability and uniqueness. Logging happens asynchronously and doesn't block responses. 404s are logged to their requested paths (e.g., a request to /api/nonexistent creates a log file in request-logs/api/nonexistent/GET/).
Route Matching
When multiple routes could match a request, blendwerk uses first-match-wins ordering. Routes are matched in the order they're discovered during directory scanning.
Static vs Dynamic Routes
Static routes (exact paths) and dynamic routes (with [param] segments) are treated equally — the first match wins. If you need a specific path to take precedence:
mocks/api/users/
├── admin/
│ └── GET.json # GET /api/users/admin (static)
└── [id]/
└── GET.json # GET /api/users/:id (dynamic)Both routes exist, and requests to /api/users/admin will match the static route if it's discovered first.
Multiple Path Parameters
You can use multiple [param] segments for nested resources:
mocks/api/users/[userId]/posts/[postId]/
├── GET.json # GET /api/users/:userId/posts/:postId
├── PUT.json # PUT /api/users/:userId/posts/:postId
└── DELETE.json # DELETE /api/users/:userId/posts/:postIdQuery Parameters
Query strings do not affect route matching — all requests to a path use the same mock response regardless of query parameters:
# All these hit the same mock: mocks/api/users/GET.json
curl http://localhost:8080/api/users
curl http://localhost:8080/api/users?page=1
curl http://localhost:8080/api/users?page=2&limit=10However, query parameters are logged when request logging is enabled, so you can see exactly what your application is requesting.
Request Logging Details
Understanding the logged fields:
| Field | Description |
|---|---|
path | The literal request path (e.g., /api/users/42) |
matched_route | The route pattern that matched (e.g., /api/users/:id) |
query | Query string if present, otherwise null |
404 requests are also logged to their requested paths. A request to /nonexistent/path creates a log file at request-logs/nonexistent/path/GET/...
Cookbook
RESTful CRUD API:
mocks/api/users/
├── GET.json # List users
├── POST.json # Create user (status: 201)
└── [id]/
├── GET.json # Get single user
├── PUT.json # Update user
└── DELETE.json # Delete user (status: 204, empty body)Error responses with custom headers:
# mocks/api/admin/GET.json
---
status: 403
headers:
X-Error-Code: FORBIDDEN
X-Error-Message: Admin access required
---
{"error": "forbidden", "message": "Admin access required"}CORS preflight response:
# mocks/api/data/OPTIONS.json
---
status: 204
headers:
Access-Control-Allow-Origin: "*"
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
---Simulating slow API (rate limiting test):
# mocks/api/heavy-operation/POST.json
---
delay: 3000
status: 202
---
{"status": "processing", "estimatedTime": "3 seconds"}Override Content-Type:
# mocks/api/legacy/GET.json - serve JSON with custom content type
---
headers:
Content-Type: application/vnd.api+json
---
{"data": {"type": "users", "id": "1"}}Docker Container Support
blendwerk properly handles running as PID 1, so you can run it directly in containers without worrying about zombie processes or signal handling. When running as PID 1 (the init process), it automatically:
- Reaps zombie processes
- Handles SIGTERM and SIGINT for graceful shutdown
- Forwards signals to child processes
This behavior is autodetected and requires no configuration:
FROM scratch
COPY blendwerk /blendwerk
COPY mocks /mocks
ENTRYPOINT ["/blendwerk"]
CMD ["/mocks"]Limitations
Memory Usage: blendwerk loads all mock response files into memory at startup (and on hot-reload). This keeps things blazing fast for development and testing, but means you probably shouldn't throw gigabyte-sized video files or massive datasets at it. If you're mocking endpoints that return large binary chunks, keep an eye on your RAM.
Production Use: Look, I think blendwerk is pretty cool, and it's great for local development, integration testing, and temporary mock services. But it's not nginx. It's not built to be a battle-hardened production web server handling millions of requests. If you find yourself thinking "maybe I should use this in production for real traffic"... maybe take a step back and consider if you're solving the right problem. That said, for what it's designed to do - providing quick, file-based API mocks - it does it well.
Text Files Only: Response files are read as UTF-8 text. Binary responses (images, PDFs) are not supported.
Static Responses: Responses are static — you cannot vary the response based on request body, headers, or query parameters. Each (method, path) combination always returns the same response.