Build a Resource API
Use the resource convention when clients benefit from one predictable set of JSON, pagination, error, idempotency, and concurrency rules. The convention is opt-in; your application still owns every business decision.
1. Define the Complete Contract
Declare all five operations with the same resource name and one unique action. The list action also declares bounded sort, filter, and cursor fields.
x-minco-resource:
name: order
action: list
defaultLimit: 20
maxLimit: 100
defaultSort: [-createdAt, -id]
sortFields: [createdAt, id]
filterFields: [status]
cursorFields: [createdAt, id]Create must declare Idempotency-Key. Update and delete must declare If-Match, plus explicit 412 and 428 Problem responses. Create, read, and update return a JSON data envelope and strong ETag; create also returns Location.
cargo minco contract check
cargo minco contract sync --check2. Preview the Specification Files
cargo minco make resource order --dry-run --jsonThe generator refuses an incomplete or inconsistent family. When the family is valid, apply the plan:
cargo minco make resource orderThe output is intentionally incomplete: it creates failing application and HTTP specifications and operation traces. It does not choose fields, write SQL, or invent successful business behavior.
Complete Request Flow
Implement one vertical slice at a time.
Create
- Parse a bounded
Idempotency-Key. - Authorize and validate before persistence.
- Atomically claim the key with a fingerprint of the command.
- Commit the resource and immutable replay snapshot together.
- Return
201; return the original result on an identical retry. - Reject the same key with a different fingerprint.
{
"data": {
"id": "018f9f9d-a8a2-7e04-9d66-cf5d9e521d71",
"customerReference": "PO-1042",
"lines": [{ "sku": "MINCO-BOOK", "quantity": 1 }],
"revision": 1,
"status": "accepted",
"createdAt": "2026-08-01T02:00:00Z",
"updatedAt": "2026-08-01T02:00:00Z"
}
}List
Parse query fields through the allowlist before calling the application port:
GET /orders?page[limit]=20&sort=-createdAt,-id&filter[status]=acceptedReturn a bounded collection. Clients treat nextCursor as opaque.
{
"data": [],
"page": {
"hasMore": false,
"nextCursor": null
}
}Read, Update, and Delete
Read returns the current strong entity tag. Send exactly that value in If-Match for update or delete.
ETag: "order:018f9f9d-a8a2-7e04-9d66-cf5d9e521d71:1"
If-Match: "order:018f9f9d-a8a2-7e04-9d66-cf5d9e521d71:1"The adapter must include the expected revision in the write predicate. A read-then-write sequence without an atomic predicate can still lose updates.
| Condition | HTTP status | Stable code |
|---|---|---|
| Header absent | 428 | precondition_required |
| Weak, repeated, or malformed tag | 400 | invalid_if_match |
| Valid but stale tag | 412 | precondition_failed |
3. Test Every Boundary
For each action, add tests in this order:
- application test proving authorization and validation fail before the port;
- domain test for each invariant or transition;
- real adapter test for transaction, idempotency, and atomic revision behavior;
- Axum
oneshottest for status, media type, headers, request ID, and body; - contract and operation trace checks.
cargo test -p orders-domain -p orders-application
cargo test -p orders-adapters -p orders-api
cargo minco explain updateOrder --json4. Keep Policy in the Application
Minco does not decide who may update, which fields are mutable, whether delete is soft or hard, how long data is retained, or what must be audited. DynamoDB uses access-pattern-specific ports rather than pretending to be a relational repository.
Use the Resource API reference for exact shapes and the orders example for exercised source.