Recipes
Stateful CRUD
A frontend under development usually needs more than canned responses: it needs to create a resource, see it in the list, edit it, and delete it. This recipe builds a complete users API on top of flags, with real 404 behavior and no backend at all.
The recipe
mock "POST /users" {
format = "json"
body = <<-EOF
{{= $id (uuid)}}
{{setFlag (append 'users:' $id) request.body}}
{{= $ids (getFlag 'users:ids')}}
{{#if $ids}}
{{setFlag 'users:ids' (append $ids ',' $id)}}
{{else}}
{{setFlag 'users:ids' $id}}
{{/if}}
{ "id": "{{$id}}" }
EOF
}
mock "GET /users" {
format = "json"
body = <<-EOF
{{= $ids (getFlag 'users:ids')}}
{{= $out ''}}
{{#if $ids}}
{{#forEach (split $ids ',')}}
{{#hasFlag (append 'users:' item)}}
{{#if $out}}{{= $out (append $out ',')}}{{/if}}
{{= $out (append $out (json (getFlag (append 'users:' item))))}}
{{/hasFlag}}
{{/forEach}}
{{/if}}
[{{$out}}]
EOF
}
mock "GET /users/{id}" {
format = "json"
body = <<-EOF
{{= $key (append 'users:' request.params.id)}}
{{#hasFlag $key}}
{{json (getFlag $key)}}
{{else}}
{{setStatus 404}}
{ "error": "User not found" }
{{/hasFlag}}
EOF
}
mock "PUT /users/{id}" {
format = "json"
body = <<-EOF
{{= $key (append 'users:' request.params.id)}}
{{#hasFlag $key}}
{{setFlag $key request.body}}
{{json request.body}}
{{else}}
{{setStatus 404}}
{ "error": "User not found" }
{{/hasFlag}}
EOF
}
mock "DELETE /users/{id}" {
format = "json"
body = <<-EOF
{{delFlag (append 'users:' request.params.id)}}
{ "deleted": true }
EOF
}Try it
$curl -X POST http://localhost:8080/users -H 'Content-Type: application/json' -d '{"name": "Alice", "email": "alice@example.com"}'
{
"id": "1c9e1e21-7a83-4d6e-9f42-2b2f6a3f8b0d"
}
$curl http://localhost:8080/users
[
{ "name": "Alice", "email": "alice@example.com" }
]
Update with PUT, delete with DELETE (your generated id will differ), and the list and detail endpoints follow along, including a 404 after deletion.
How it works
- Each record is one flag:
users:<id>stores the submitted body as an object, written withsetFlagand rendered back with thejsonhelper. users:idskeeps a comma-separated list of ids, which is what makes the list endpoint possible. ThePOSTappends to it, treating the empty case separately becauseappendon a missing value would produce a leading comma.- The list endpoint splits the id list, skips ids whose record flag no longer exists (deleted users), and accumulates the JSON into the
$outvariable, adding commas only between elements. Accumulation is used instead of{{^isLast}},{{/isLast}}because the last id in the list might be a deleted record, which would leave a trailing comma. hasFlagguards the detail and update endpoints, turning missing records into404s withsetStatus.
Deleting a user removes the record flag but leaves its id in
users:ids. The list endpoint tolerates the stale id, so nothing breaks; the ids flag just grows over time. Delete it in the flags panel or with a reset mock when you want a clean slate.Variations
- The stored record is exactly the submitted body, so responses do not include the generated id. If your client expects it, render the fields explicitly:
{ "id": "{{request.params.id}}", "name": "{{pick (getFlag $key) 'name'}}" }. - Add a reset endpoint for test suites: a
DELETE /usersmock that calls{{delFlag 'users:ids'}}. - On a storeless instance all of this state disappears on restart, which is often exactly what you want locally. Run with Redis when the data should survive; see Persistence and Redis.
Related
For a read-only collection served from fixtures instead of runtime state, use List and Detail From Data. For accumulating submissions without update and delete, the simpler Append to a List recipe is enough.