A Mock Backend That Pays Attention: Path Params and Request Templating
Every mock API gives itself away the same way. The list page works: GET /posts returns twenty plausible rows, the table renders, everyone is happy. Then somebody opens a detail page — fetch(\/posts/$`)— and the mock answers with a 404, because as far as it knows you have defined one endpoint in your life and its name is/posts`.
The workaround is always the same, too. Somebody creates /posts/1. Then /posts/2. Somewhere around /posts/7, two things are clear: the endpoint list has to be maintained by hand, and every id in the feed that wasn't pre-created — most of them, Faker being generous with ids — is still a 404. The mock isn't broken. It's deaf. It has no way to know which post you asked for, and no way to mention it back if it did.
Mimicry just shipped the two features that fix exactly this, and they belong together: path params, which let an endpoint have a hole in it (/posts/:id), and request templating, which lets a response refer to the request that triggered it ({{request.path.id}}). One gives the URL a voice, the other gives the answer a memory. A mock stops feeling fake at the exact moment its reply changes because the question did.

Three different requests, three different answers, one endpoint. Each highlighted value comes from a different part of the request that triggered it: id from the path, source from the query string, title from the JSON body. Only editor is Faker. That's the whole feature.
This post builds a fake backend for a small publishing app: detail pages, nested comment routes, search, a session endpoint, a POST that acknowledges what you sent, and a 404 that knows what it's 404-ing about. Simple mocks, no code, a handful of curls. Project slug my-blog, base URL https://mimicry.rest/m/my-blog.
The two primitives, in thirty seconds
Path params. The path of a simple mock can contain :name segments. /posts/:id matches /posts/42, /posts/hello-world — any single segment in that position. Up to 8 params per path, lowercase names, each used once, one segment each: :id will never swallow a/b. Matching is exact-first: if you also define /posts/drafts, that URL goes to the exact endpoint and everything else falls through to :id. And two endpoints with the same shape and method (/users/:id vs /users/:user_id) are rejected at save time with a 409, which is the system saving you from a confusing afternoon.
Request templating. Any static value in a response schema can contain tags that get replaced with data from the incoming request:
| Tag | Resolves to |
|---|---|
{{request.path.id}} | the value captured by :id in the path |
{{request.query.q}} | the ?q= query parameter (first value if repeated) |
{{request.header.authorization}} | a request header, case-insensitive |
{{request.body.user.email}} | a field of the JSON body — dotted paths, array indices (items.0.name) |
{{string.uuid}} | Faker data, resolved once at generation time — same syntax as schema types |
Tags also work inside larger strings, not just alone: user-{{request.path.id}} comes back as user-42.
One distinction carries the whole post: request.* tags resolve per request, after anything cached. Faker tags resolve once, at generation time, and are deterministic if the endpoint has a seed. Frozen where you want stability, alive where you want reflection. Everything below is that one sentence, demonstrated six ways.
A detail page that answers the right question
First endpoint: GET /posts/:id. In the schema editor, set id to a static value containing {{request.path.id}} and leave the rest as ordinary Faker fields.
curl https://mimicry.rest/m/my-blog/posts/42
{
"id": "42",
"title": "Architecto minima et sunt",
"author": "Gina Feest",
"publishedAt": "2026-08-14T16:02:11.307Z"
}
Whatever id you ask for comes back in the payload. Links resolve, keys line up, the "Post #42" header says 42. Click around the feed and every detail page agrees with the URL that produced it — an entire class of "wait, that's the wrong row" bugs stops existing.
Two honest caveats. The echoed value comes back as a string — "id": "42", not 42 — so if a component does arithmetic on ids, that's on you. And this is still not a database: the other fields regenerate on every call. If you need the same body twice, give the endpoint a seed (the Faker half freezes; more on the strange, excellent consequences below) or model posts as a CRUD resource instead.
Exact beats parametric, and nested routes ride along
A publishing app wants /posts/drafts — a fixed, hand-written list — living next to /posts/:id. No conflict: define both. The exact match is always checked first, so /posts/drafts goes to the drafts endpoint and every other single segment falls through to the parametric one. Definition order doesn't matter; match specificity does.
It scales to depth, too:
GET /posts/:post_id/comments/:comment_id
Both params are captured, and both are addressable in the response:
curl https://mimicry.rest/m/my-blog/posts/42/comments/7
{
"postId": "42",
"commentId": "7",
"author": "Marcus King",
"body": "This finally matches the URL."
}
Breadcrumbs like "Post 42 → Comment 7" now render from the response instead of from a useState you promised yourself you'd remove.
A search box that gets an answer back
Search preview: GET /posts/search, with query as a static value containing {{request.query.q}}.
curl "https://mimicry.rest/m/my-blog/posts/search?q=mimicry"
{
"query": "mimicry",
"results": [{ "title": "Voluptas mimicry explicabo", "score": 0.92 }]
}
Why echo a query the caller just sent? Because your UI renders it. "Results for mimicry" — with a deaf mock that string is hardcoded, and the day the binding to data.query breaks, you will never notice. With an echo, the header has to prove it's wired. I've caught dead bindings this way; on screen they look exactly like working ones.
Note that /posts/search is an exact endpoint. If we hadn't defined it, that URL would have matched /posts/:id with id search, and the detail page would have cheerfully rendered a post called search. Exact-first exists so you don't live in that world.
A session endpoint that recognizes its caller
GET /session, one static field echoing a header: "token": "{{request.header.authorization}}".
curl https://mimicry.rest/m/my-blog/session -H "Authorization: Bearer dev-token-123"
{
"user": { "name": "Ada Owner", "role": "admin" },
"token": "Bearer dev-token-123"
}
Header lookup is case-insensitive — Authorization, authorization, same result — and dashed names work as-is ({{request.header.x-request-id}}). My usual use: confirming the auth layer actually attaches the header before the request leaves the app. When it doesn't, the echo shows an empty string where the token should be, which is a much better conversation starter than "401 in staging, can't reproduce."
It's an echo, to be clear. No JWT is parsed, nothing is validated. The mock repeats what it heard — and sometimes that's all the frontend needs to feel real.
A POST that acknowledges what you sent
POST /comments. Response schema: id as a static {{string.uuid}}, author echoing {{request.body.author}}, email echoing {{request.body.user.email}} — a dotted path into the body — the rest Faker.
curl -X POST https://mimicry.rest/m/my-blog/comments \
-H "Content-Type: application/json" \
-d '{ "author": "Jane Doe", "user": { "email": "[email protected]" }, "body": "Great post" }'
{
"id": "9f1c4a02-9d3c-4b8e-a1f7-77c2e5d0a3b6",
"author": "Jane Doe",
"email": "[email protected]",
"postedAt": "2026-08-18T09:41:22.004Z"
}
Your optimistic-update flow now has something honest to reconcile against: an id the "server" assigned, the fields it "stored", a timestamp — and the email the user actually typed, nested path and all.
Now the subtle part. Give this endpoint a seed. The Faker half — id, timestamp, generated filler — is produced once, cached, and identical on every call. The request half is interpolated after that cache, fresh each time. Post two different comments and the echoed fields follow the requests while the generated ones hold still. For a visual-regression test that's exactly the combination you want: stable layout data, varying user data, from one stateless endpoint.
Teaching the mock to say no
The last piece of a believable backend is refusal. Conditional responses — rules that return a canned status and body when a condition on the request matches — gained a path source. On GET /posts/:id, add a rule: source path, key id, operator eq, value 999, response 404 with this body:
{ "error": "not_found", "postId": "{{request.path.id}}" }
Yes, conditional bodies are templated too:
curl https://mimicry.rest/m/my-blog/posts/999
{
"error": "not_found",
"postId": "999"
}
Your error state finally gets exercised by a 404 that is coherent — the id in the payload matches the URL, the way real APIs embarrass you. A second rule with contains (id contains archived → 410 Gone, "This post has been archived") covers the other flavor of dead link. Rules evaluate top to bottom, first match wins, no match falls back to the default response. The full tour of conditional responses — roles, empty states, suspended accounts — is its own post; the path source slots right into it. One limit to know: path works on simple mocks only, not on CRUD resources.
The fine print
- A tag whose value is missing resolves to an empty string, not an error.
{{request.query.serach}}— typo — silently becomes"". The most common way to lose ten minutes. /posts/:idand/posts/:slugon the same method can't coexist (409, same shape). Same shape on different methods is fine.- If a CRUD resource owns
/posts, it also owns/posts/123— CRUD matching runs first, and your parametric endpoint never fires. Choose prefixes accordingly. - Response size caps are re-checked after interpolation. Echo something huge into a capped role and you'll get a 403, not the echo.
- Params capture exactly one segment, names are lowercase, max 8 per path. And there's no whole-body echo (
{{request.body}}) — leaf fields only, for now.
What you've got when you're done
A feed. Detail pages that agree with their URLs. Nested comment routes. A search that answers in your own words. A session that recognizes its caller. A POST that acknowledges you. A 404 with an opinion. None of it is a database; all of it listens.
That last part is the whole feature. A mock that ignores its request is a fixture with a URL — fine for layout, useless for behavior. Path params and request templating are how a fake backend pays attention, and the moment it starts paying attention is the moment your frontend stops being surprised by the real one.
You can build everything in this post as a guest at mimicry.rest in a few minutes, no account needed. For the wider playbook, start with How to Mock an API. And if the fixture-to-endpoint migration is the thing you're currently living, Static JSON Mocks Are Not Enough is your story too.
Ready to try it yourself?
Stop waiting for the backend. Use Mimicry to create a mock API, launch a mock REST API generator, or share a hosted mock API with your team.
Create a Mock API