An HTTP request carries a method, target, headers, and sometimes a body. The server answers with a status, headers, and an optional representation. A method is an operation contract, not a decorative prefix on a URL. A retrieval endpoint should not change server state merely because its path contains an action word; a write endpoint should validate input before it records a change. A status tells the client whether to handle a result, missing resource, validation error, or server failure. The response body can explain the result, but clients must not infer success from the presence of JSON alone. This small router models GET as a read and POST as a bounded write. It returns concrete status codes and records only validated messages.
HTTP requests: keep method, status, and body contracts separate
Case study
A maintenance board stores one pump report under ID 47. Reading it returns status 200 and the saved text. Asking for report 83 returns 404. Posting a blank report returns 422 and leaves storage unchanged; posting a valid report returns 201 with the new ID. The caller must distinguish a 404 from a temporary network failure because retrying one cannot create a missing report. A real server would also authenticate the caller, limit body size, and persist the record; the in-memory dictionary here isolates method and status behavior.
Working contract
const reports = new Map([[47, "pump checked"]]);
function handleRequest(method, reportId, message) {
if (method === "GET") {
return reports.has(reportId)
? { status: 200, body: reports.get(reportId) }
: { status: 404, body: "Report not found" };
}
if (method !== "POST") return { status: 405, body: "Method not allowed" };
if (typeof message !== "string" || !message.trim() || message.length > 160) {
return { status: 422, body: "Report text is required" };
}
if (reports.has(reportId)) return { status: 409, body: "Report already exists" };
reports.set(reportId, message.trim());
return { status: 201, body: reportId };
}
console.log(handleRequest("GET", 47).status);
console.log(handleRequest("POST", 83, " ").status);
console.log(handleRequest("POST", 83, "valve inspected").status);
console.log(reports.get(83));Observed output
200
422
201
valve inspectedCost and tradeoffs
Dictionary reads and writes take expected O(1) time for a bounded key and use O(N) storage for N reports. Validation scans the submitted message, so its time is O(L) for message length L. Network transfer and serialization add costs not represented by this synchronous model. A response should include an appropriate content type and a bounded payload; this example prints only status and record ID to keep the trace deterministic. HTTP method semantics remain relevant even if a client library hides the wire format.
Common Mistakes
- Do not perform a state-changing write behind a GET endpoint.
- Do not treat every response containing JSON as a success.
- Do not return 201 when validation prevented creation.
- Do not retry a missing-resource response as though it were a transport interruption.
Continue through the stack
HTML forms: choose GET for retrieval and POST for a state change; JavaScript Tutorial; Form submission: validate on the server and return field errors; Fetch requests: separate HTTP failure, transport failure, and cancellation.
Failure trace
A dashboard sends GET to a path named create-inspection and the server writes a row. A browser prefetch or an automated checker can now create records without anyone pressing Save. The route name does not alter the method's meaning. Move the mutation to a write method, validate its body and permission, and return a status that matches the committed effect. On failure, the client must not infer success merely because a JSON object arrived.
Verification
- Issue the read endpoint twice and assert the record count never changes.
- Send a blank write body and verify both the error status and unchanged storage.
- Simulate a successful write response being lost, then document how a retry avoids a duplicate.
Decision note
A 4xx response is an observed server decision, while a transport failure has no response to inspect. Keep these branches separate in logs and UI; the recovery action for missing data differs from the action for a dropped connection.
