Reads and writes
MeshQL’s pitch is REST underneath, GraphQL-style field selection on top. That applies to the whole API:
- Reads — resource URLs +
X-Mesh-Queryfor which fields/joins to return - Writes — the same resource URLs with
PUT/POST/DELETE - No GraphQL-style nested mutation body (
$connect/$disconnect/$createMany) - No parallel write RPC like
/mesh/write
One mental model: resource-per-URL. Selection is a read concern. Mutations are normal REST.
Why this shape
Section titled “Why this shape”- Consistency — reads are already resource-per-URL and cacheable. Writes match, or the library teaches two models.
- Positioning — “REST underneath, field selection on top.” A nested mutation DSL would break that on the write side.
- Simplicity — REST doesn’t need
$connect/$disconnect/$createMany.
POSTto a sub-resource collection is create.
DELETEon a sub-resource URL is disconnect / delete.
Expressiveness lives in URLs, not new operators.
The e2e path
Section titled “The e2e path”# Update user's own fields — body is the patch; header selects the response shapePUT /mesh/user/1X-Mesh-Query: <base64 $select for the response shape>Content-Type: application/json
{ "name": "Ada Lovelace" }
# Create a new token under this userPOST /mesh/user/1/tokensContent-Type: application/json
{ "accessToken": "tok_new" }
# Delete one specific tokenDELETE /mesh/user/1/tokens/42
# Connect an existing business (replace the to-one link)PUT /mesh/user/1/businessContent-Type: application/json
{ "entityId": "biz_42" }
# Disconnect itDELETE /mesh/user/1/business| Verb | Path | Meaning |
|---|---|---|
GET |
/mesh/{entity} |
List read (+ X-Mesh-Query) |
GET |
/mesh/{entity}/{id} |
Point read |
PUT |
/mesh/{entity}/{id} |
Update scalars; optional X-Mesh-Query for shaped response |
POST |
/mesh/{entity}/{id}/{relation} |
Create a many child (or link for M2M) |
DELETE |
/mesh/{entity}/{id}/{relation}/{childId} |
Delete / unlink one many child |
PUT |
/mesh/{entity}/{id}/{relation} |
Connect / replace a one relation (entityId) |
DELETE |
/mesh/{entity}/{id}/{relation} |
Disconnect a one relation |
Many-to-many uses the same URLs (junction insert/delete under the hood when the join has through):
POST /mesh/post/1/tags{ "entityId": 7 }
DELETE /mesh/post/1/tags/7Reads (shipped)
Section titled “Reads (shipped)”| Method | Path | Purpose |
|---|---|---|
GET |
/mesh/{entity} |
List — selection in X-Mesh-Query |
GET |
/mesh/{entity}/{id} |
Point read |
POST |
/mesh |
Complex read only (query in body — not a mutation RPC) |
GET /mesh/user/1X-Mesh-Query: <base64 selection>Writes (target — not in core yet)
Section titled “Writes (target — not in core yet)”| Target | Today in @meshql/http |
|---|---|
PUT /mesh/{entity}/{id} update |
Still a point-read transport (no body patch) — will become the update above |
POST /mesh/{entity}/{id}/{relation} |
Path used for file upload (:field) today — relation create is the mutation profile |
DELETE /mesh/... |
405 until writes land |
Until core writes ship, implement these same /mesh/... paths in your app (or a thin adapter) so clients already match the contract. Do not introduce a second write API.
What we are not doing
Section titled “What we are not doing”| Anti-pattern | Why not |
|---|---|
GraphQL mutation { updateUser { … } } |
Wrong model for MeshQL |
Nested $connect / $disconnect / $createMany in a body |
URLs already express that |
POST /mesh/write as the product write API |
Second mental model; not REST resource-per-URL |
Where to go next
Section titled “Where to go next”- HTTP adapters — routes registered today
- Thinking in MeshQL — selection → JoinPlan → shaper
- FAQ — How do mutations work?