Recipes
List and Detail From Data
The most common pair of endpoints in any API: a collection and a detail view of the same resources. This recipe serves both from one data block, so the fixtures live in a single place and unknown ids return a proper 404.
The recipe
data "products" {
product {
id = 1
content = <<-EOF
{
"id": 1,
"name": "Widget",
"price": 9.99
}
EOF
}
product {
id = 2
content = <<-EOF
{
"id": 2,
"name": "Gadget",
"price": 24.99
}
EOF
}
product {
id = 3
content = <<-EOF
{
"id": 3,
"name": "Doohickey",
"price": 4.99
}
EOF
}
}
mock "GET /products" {
format = "json"
body = <<-EOF
[
{{#forEach data.products.product}}
{{item.content}}{{^isLast}},{{/isLast}}
{{/forEach}}
]
EOF
}
mock "GET /products/{id}" {
format = "json"
body = <<-EOF
{{= $id request.params.id}}
{{= $found false}}
{{#forEach data.products.product}}
{{#is item.id $id}}
{{= $found true}}
{{item.content}}
{{/is}}
{{/forEach}}
{{#unless $found}}
{{setStatus 404}}
{ "error": "Not found" }
{{/unless}}
EOF
}Try it
$curl http://localhost:8080/products/2
{
"id": 2,
"name": "Gadget",
"price": 24.99
}
$curl -i http://localhost:8080/products/99
HTTP/1.1 404 Not Found
...
{
"error": "Not found"
}
How it works
- Each product carries an
idfor lookups and acontentheredoc holding the full payload. The list endpoint just concatenates the payloads withforEachand{{^isLast}},{{/isLast}}commas. - The detail endpoint extracts the path parameter to
$idbefore the loop, because insideforEachthe context changes andrequestis out of scope. iscompares with loose equality, which matters here: the path parameter is the string"2"while the data block id is the number2.eqwould never match.$foundstarts false and flips when a product matches. Variables have global scope, so the assignment inside the loop is visible to theunlessblock after it, which turns no-match into a404.
This pattern is the read-only sibling of Stateful CRUD. The two combine well: serve the baseline catalog from data and layer runtime changes on top with flags.
Variations
- For paginated lists, extract
data.products.productto a variable and combineslice,length, and query parameters. The helpers are in the Bigodon reference. - To filter the list by a query parameter, reuse the detail endpoint's loop-and-match shape, accumulating matches instead of rendering a single one.