Inquiry Question 2: How can data be better visualised using a web browser?
Design and consume RESTful APIs that exchange JSON, including resource modelling, request methods and status codes
A focused answer to the HSC Software Engineering Module 2 dot point on REST APIs. Resource modelling, JSON, HTTP methods mapped to CRUD, status codes, the worked example, and the traps markers look for.
Reviewed by: AI editorial process; not yet individually human-reviewed
Have a quick question? Jump to the Q&A page
What this dot point is asking
NESA wants you to design a REST API: resource paths, HTTP methods, JSON payloads, status codes. You should also be able to consume one from JavaScript (in the browser) or Python (requests).
The answer
What REST is
REST (Representational State Transfer) is an architectural style for APIs:
- Resources are the nouns of the system: users, posts, tasks, products.
- Each resource has a URL (
/api/tasks,/api/tasks/42). - HTTP methods are the verbs: GET reads, POST creates, PUT/PATCH updates, DELETE removes.
- The representation of a resource (the body returned by the server) is typically JSON.
- The API is stateless: each request carries everything needed to process it (usually a token in the Authorization header).
Designing endpoints
Use plural nouns for collections, IDs for items:
| Method | Path | Action | Success status |
|---|---|---|---|
| GET | /api/tasks |
List tasks (optionally filtered) | 200 |
| POST | /api/tasks |
Create a task | 201 |
| GET | /api/tasks/{id} |
Read one task | 200 |
| PUT | /api/tasks/{id} |
Replace a task | 200 |
| PATCH | /api/tasks/{id} |
Update some fields | 200 |
| DELETE | /api/tasks/{id} |
Delete a task | 204 |
Avoid verbs in URLs (/api/getTasks, /api/deleteTask). The HTTP method already conveys the action.
An owned diagram makes the resource/method/status mapping concrete for a single collection:
JSON
The standard payload format:
{
"id": 42,
"title": "Study Module 2",
"due": "2026-06-15",
"done": false,
"tags": ["software-engineering", "study"]
}
Use camelCase or snake_case consistently. Use ISO 8601 (2026-06-15, 2026-06-15T10:00:00Z) for dates and times.
Status codes
- 2xx success: 200 OK, 201 Created (with
Locationheader), 204 No Content. - 4xx client error: 400 Bad Request, 401 Unauthorized (missing or invalid credentials), 403 Forbidden (logged in but not allowed), 404 Not Found, 409 Conflict (duplicate), 422 Unprocessable Entity, 429 Too Many Requests.
- 5xx server error: 500 Internal Server Error, 503 Service Unavailable.
A worked endpoint
A Flask handler for POST /api/tasks:
@app.post("/api/tasks")
@require_login
def create_task():
data = request.get_json(silent=True) or {}
title = (data.get("title") or "").strip()
due = (data.get("due") or "").strip()
if not (1 <= len(title) <= 200):
abort(400, "title length 1-200")
with db() as conn:
cur = conn.execute(
"INSERT INTO tasks (user_id, title, due) VALUES (?, ?, ?)",
(request.user_id, title, due or None),
)
task_id = cur.lastrowid
return jsonify(
id=task_id, title=title, due=due or None, done=False
), 201, {"Location": f"/api/tasks/{task_id}"}
Consuming from the browser
async function createTask(title, due) {
const response = await fetch("/api/tasks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({title, due}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
Consuming from Python
From a Python script or another back-end service, the requests library is the conventional choice for calling a REST API. It handles connection pooling and JSON parsing for you.
import requests
response = requests.post(
"https://api.example.com/tasks",
headers={"Authorization": f"Bearer {token}"},
json={"title": "Study Module 2", "due": "2026-06-15"},
)
response.raise_for_status()
task = response.json()
Filtering and pagination
GET on a collection takes query string filters:
GET /api/tasks?done=false&due_before=2026-07-01&limit=20&offset=40
The server applies the filters in the WHERE clause and limits the page size.
Versioning
Plan for change. Include the version in the URL prefix:
GET /api/v1/tasks
GET /api/v2/tasks
or in a custom header (X-API-Version: 2).
Security
- HTTPS only.
- Authenticate every request (Authorization header).
- Validate every field of the request body.
- Authorise object-by-object (does this user own this task?).
- Rate limit per token and per IP.
Exam-style practice questions
Practice questions written in the style of NESA exam questions on this dot point, with worked answer explainers. The year tag is the paper they imitate, not the source.
2024 HSC5 marksDesign a RESTful API for a simple todo list. List the endpoints, methods and status codes. Show one example request and response in JSON.Show worked answer →
Endpoints:
| Method | Path | Purpose | Success status |
|---|---|---|---|
| GET | /api/tasks | List all tasks | 200 OK |
| POST | /api/tasks | Create a task | 201 Created |
| GET | /api/tasks/:id | Read one task | 200 OK |
| PUT | /api/tasks/:id | Replace a task | 200 OK |
| PATCH | /api/tasks/:id | Update fields | 200 OK |
| DELETE | /api/tasks/:id | Delete a task | 204 No Content |
Error cases: 400 (bad input), 401 (not logged in), 403 (not allowed), 404 (not found).
Example - creating a task:
POST /api/tasks
Authorization: Bearer ...
Content-Type: application/json
{"title": "Study Module 2", "due": "2026-06-15"}
Response:
HTTP/1.1 201 Created
Location: /api/tasks/42
Content-Type: application/json
{"id": 42, "title": "Study Module 2", "due": "2026-06-15", "done": false}
Markers reward consistent resource-named paths (plural noun for the collection), correct method/code combinations (POST returning 201 with Location header, DELETE returning 204), a real JSON example with status line, and recognising that PATCH (partial update) and PUT (full replace) are distinct.
Practice questions
Original practice questions graded from foundation to exam level, each with a full worked solution. Try them before revealing the solution.
foundation3 marksA blog API needs to (a) list all comments on post 8, (b) add a new comment to post 8, and (c) delete comment 55 on post 8. For each, state the HTTP method, the resource path, and the success status code.Show worked solution →
| Action | Method | Path | Success status |
|---|---|---|---|
| List comments | GET | /api/posts/8/comments |
200 OK |
| Add comment | POST | /api/posts/8/comments |
201 Created |
| Delete comment | DELETE | /api/posts/8/comments/55 |
204 No Content |
Marking criteria: 1 mark for the correct method on each of the three rows (all three needed for the mark), 1 mark for nesting the comment under its parent post with a plural collection noun, 1 mark for the correct success status on each row (201 with no body content expectation, 204 with no body).
foundation3 marksExplain why the endpoint POST /api/deletePost?id=19 is not RESTful, and rewrite it as a RESTful request (method, path, and success status).Show worked solution →
Why it is not RESTful. The verb "delete" appears in the URL path, duplicating the job of the HTTP method, and the method used is POST rather than DELETE, so the request does not map onto the standard verb-to-action convention that REST tooling and caches rely on.
RESTful rewrite.
The resource (post 19) is identified purely by its path; the HTTP method DELETE conveys the action, so no verb is needed in the URL.
Marking criteria: 1 mark for identifying the verb-in-URL problem, 1 mark for identifying the wrong HTTP method (POST instead of DELETE), 1 mark for a correct RESTful rewrite with method, path and status code.
core4 marksA junior developer's request log shows:
| Line | Method | Path | Status returned |
|---|---|---|---|
| 1 | GET | /api/orders/17 | 200 |
| 2 | GET | /api/deleteOrder?id=17 | 200 |
| 3 | POST | /api/orders | 200 |
| 4 | DELETE | /api/orders/17 | 204 |
Identify the TWO log lines that break REST conventions, and state which convention each one breaks.
Show worked solution →
Line 2 breaks the convention. It uses GET (meant to be safe and read-only) to actually delete a resource, and puts the verb "delete" in the URL instead of using the DELETE method on /api/orders/17. A GET that mutates data is dangerous because browsers, crawlers and caches can trigger GET requests without the user intending a delete.
Line 3 breaks the convention. A successful POST that creates a new order should return 201 Created (with a Location header pointing to the new resource), not 200 OK, which is reserved for a successful read or update of an existing resource.
Lines 1 and 4 are correctly formed (GET returning 200 for a read, DELETE returning 204 for a removal with no body).
Marking criteria: 1 mark for identifying line 2 as faulty, 1 mark for correctly explaining why (GET should be safe/read-only; verb in URL), 1 mark for identifying line 3 as faulty, 1 mark for correctly explaining why (POST creation should return 201, not 200).
core5 marksDesign a REST API for a school library's book-loan system. Give a method/path/status table covering listing books, borrowing a book (creating a loan) and returning a book (ending a loan), and show one example JSON request and response for creating a loan.Show worked solution →
Endpoint table.
| Method | Path | Purpose | Success status |
|---|---|---|---|
| GET | /api/books |
List all books | 200 OK |
| GET | /api/loans?studentId=51 |
List a student's active loans | 200 OK |
| POST | /api/loans |
Borrow a book (create a loan) | 201 Created |
| PATCH | /api/loans/{id} |
Return a book (mark the loan ended) | 200 OK |
Example - creating a loan.
POST /api/loans
Content-Type: application/json
{"bookId": 204, "studentId": 51}
Response:
HTTP/1.1 201 Created
Location: /api/loans/900
{"id": 900, "bookId": 204, "studentId": 51, "returned": false}
Marking criteria: 1 mark for a resource-named GET on books, 1 mark for a correctly designed POST that creates a loan resource, 1 mark for using PATCH (not DELETE) to mark a return since the loan record is kept, not removed, 1 mark for correct status codes throughout, 1 mark for a complete, correctly formatted example JSON request and response pair with a Location header.
core4 marksA GET /api/users/12 response currently returns {"id": 12, "name": "Priya", "passwordHash": "a94...", "isInternalAdmin": false}. Explain the security problem and show the corrected JSON that should be returned instead.Show worked solution →
The problem. The response leaks two fields that should never reach the client: passwordHash (an attacker who intercepts or is served this response gains material to attempt an offline crack) and isInternalAdmin (an internal implementation flag that exposes system structure and could be tampered with if the client ever echoes it back on a write).
Corrected response.
{"id": 12, "name": "Priya"}
The API should map the database row to an explicit "public view" of the resource rather than serialising the raw row, so new sensitive columns added later do not automatically leak.
Marking criteria: 1 mark for identifying the password hash leak, 1 mark for identifying the internal flag leak, 1 mark for explaining the general principle (map explicitly, never serialise raw rows), 1 mark for a corrected JSON example containing only safe fields.
exam6 marksExplain the difference between PUT and PATCH, including the idea of idempotency. Justify, with reference to a todo API, which method you would choose to mark a single task's 'done' field as true, and explain the consequence of choosing the wrong one.Show worked solution →
This is a 6-mark explain-and-justify: markers reward a correct technical distinction, correct use of idempotency, and a reasoned choice with a consequence, not just a definition.
- PUT vs PATCH
- PUT is defined to REPLACE the entire resource representation at a URL: any field the client omits from the PUT body is treated as absent (often reset to null or a default) in the stored resource. PATCH is defined to apply a PARTIAL update: only the fields present in the PATCH body are changed, and every other field is left untouched.
- Idempotency
- Both PUT and DELETE are idempotent by definition: sending the same PUT request N times leaves the resource in the same final state as sending it once, because it always sets the full state to the same values. PATCH is not guaranteed idempotent in general (e.g. a PATCH that increments a counter gives a different result each time it is repeated), though a PATCH that simply sets a field to a fixed value behaves idempotently in practice.
- Choice for the todo API
- To mark just the
donefield true on/api/tasks/{id}, PATCH is the correct choice:PATCH /api/tasks/42 {"done": true}changes only that one field and leavestitle,dueand any other fields exactly as they were. - Consequence of using PUT instead
- If a client sent
PUT /api/tasks/42 {"done": true}without re-sendingtitleanddue, a server that implements PUT correctly (full replace) would wipe those fields to null or default, silently destroying data the client did not intend to touch. This is a common real bug when developers use PUT as if it were PATCH.
Marker's note: full marks require (1) a correct definition of both methods in terms of replace vs partial update, (2) a correct statement of idempotency for both PUT and DELETE, (3) PATCH justified as the correct choice with a syntactically plausible request, and (4) a concrete, correctly reasoned consequence of using PUT wrongly (data loss on omitted fields), not just "it would be wrong".
exam7 marksA legacy internal API exposes /getAllUsers (GET), /removeUser?id=9 (GET), and /saveUser (POST, used for both creating and editing a user), and every one of these endpoints always returns HTTP 200, even when the operation fails, with the real result reported inside a JSON field called "ok". Evaluate this design against REST principles and propose a redesigned set of endpoints, methods and status codes.Show worked solution →
This is a 7-mark evaluate-and-redesign: markers reward a structured critique against named REST principles followed by a concrete, internally consistent redesign, not just a list of complaints.
Evaluation.
- Verbs in URLs.
/getAllUsers,/removeUserand/saveUserall encode the action in the path, duplicating the job the HTTP method should do; this makes the API harder to route through standard tooling (proxies, caches, generated clients) that expect method-driven behaviour. - Wrong or overloaded methods.
/removeUseruses GET to perform a destructive delete, which is unsafe because GET requests can be triggered by prefetching, crawlers or browser history without user intent./saveUseroverloads a single POST endpoint for two different operations (create and edit) that REST would separate by method (POST to create, PATCH or PUT to edit an existing user). - Status codes carry no meaning. Returning 200 unconditionally, with success/failure buried in a body field, breaks every piece of standard tooling that inspects the HTTP status line (load balancers, monitoring, fetch/axios error handling, caching rules), and forces every consumer to parse the body just to know if the call worked.
Redesign.
| Method | Path | Purpose | Success status |
|---|---|---|---|
| GET | /api/users |
List all users | 200 OK |
| POST | /api/users |
Create a user | 201 Created |
| GET | /api/users/{id} |
Read one user | 200 OK |
| PATCH | /api/users/{id} |
Edit an existing user | 200 OK |
| DELETE | /api/users/{id} |
Remove a user | 204 No Content |
Failures return the matching error status (400 invalid input, 404 unknown user) with an error body, instead of 200 with a hidden "ok" field.
Marker's note: top marks require (1) naming at least two distinct REST violations with the mechanism explained (not just "it's bad practice"), (2) explicitly identifying the unsafe GET-that-deletes as a correctness/security issue, (3) a complete redesigned table using plural-noun paths and the correct method-to-action mapping, and (4) explaining why status codes must reflect real success/failure rather than always returning 200.
