The request
"Load the product list from another program." Ask who owns product names and prices, what an empty list means, and what to display when that program is unavailable. A URL alone is not a contract.
For this module, build a local service. It owns a fictional catalogue. The browser fetches products and lets a learner inspect them. The existing manually entered quote still works independently. Server-authoritative order submission comes later.
Our first contract
| Request | Response | Meaning |
|---|---|---|
| GET /api/products | 200, JSON { "products": [...] } |
Available products |
| GET /api/products?mode=empty | 200, JSON { "products": [] } |
Successful empty catalogue |
| GET /api/products?mode=error | 503, JSON { "error": "unavailable" } |
Temporary service failure |
| Unknown route | 404 | No such resource |
A product has an id, name and non-negative safe integer unitPriceCents. Product IDs must be unique. Extra fields may be ignored; missing or invalid required fields reject the response. An empty array is valid. Missing products is not an empty array.
Read a request before writing one
Open the Network panel. Identify the method, URL, status, content type and response body. JSON is a data representation, not proof that its values satisfy your contract. A server can return a valid JSON string where you expected an object.
A request has at least three failure boundaries: no usable response, an unsuccessful HTTP status, and a successful status with an unusable body. The person may see one recoverable message, while your development evidence distinguishes all three.
Do not use mode: 'no-cors' as a fix for unreadable JSON. In this lab the page and API will share one origin through one local server. Scheme, host and port together define that origin; two different ports are different origins.
Decide the interface states
Write the state model before code: idle, loading, ready, empty, error. Keep the catalogue state separate from the quote state. A catalogue failure must not erase the person's name or silently replace a selected price with zero.
Our initial policy clears old catalogue results when a new load starts. This avoids presenting stale data as current. A later stale-while-refreshing design is possible, but would need an explicit freshness label and rules about selecting stale products.
Evidence to submit
Sketch each of the five states and write one sentence describing the action available in it. For an empty catalogue, offer Reload and explain there are no products; for an error, offer Retry and explain the load failed. Those messages communicate different facts.
Your counterexample: a response of { "products": null } must trigger a contract error, not "No products available."