API & webhooks
REST API Practice Example: Run a Small Local Notes Service
Run a read-only Node.js API on localhost to practice resource and collection GET requests, missing resources, unsupported methods and predictable resets.
Use a fixture whose behavior you control
A local practice API gives you repeatable responses without depending on a public demo service or sending test traffic to an unrelated site. This example exposes two synthetic notes and a read-only collection endpoint. It is intentionally small enough that you can inspect every route.
Save the code as practice-api.mjs in a new practice directory and run node practice-api.mjs with a supported Node.js installation. The .mjs extension makes the import syntax explicit. If port 3000 is occupied, choose another local port consistently in the code and your requests.
Start the read-only service
import {createServer} from "node:http";
const notes = [
{id: "7", title: "River & rail"},
{id: "9", title: "Mountain map"}
];
createServer((req, res) => {
const url = new URL(req.url, "http://127.0.0.1:3000");
const send = (status, value) => {
res.writeHead(status, {"Content-Type": "application/json"});
res.end(JSON.stringify(value));
};
if (req.method !== "GET") {
res.setHeader("Allow", "GET");
return send(405, {error: "Use GET in this practice API."});
}
if (url.pathname === "/notes") {
const query = (url.searchParams.get("q") || "").toLowerCase();
return send(200, {items: notes.filter(note =>
note.title.toLowerCase().includes(query))});
}
const match = /^\/notes\/([A-Za-z0-9_-]+)$/.exec(url.pathname);
const note = match && notes.find(item => item.id === match[1]);
return note ? send(200, note) : send(404, {error: "Note or route not found."});
}).listen(3000, "127.0.0.1", () => console.log("Practice API ready."));
The server binds to the loopback address, so this is a local exercise rather than a public deployment recipe. The data lives in memory and is never modified by the supported routes. The Node HTTP documentation describes the server interface used here.
Practice a resource request and a filtered collection
Request http://127.0.0.1:3000/notes/7 and expect the object with string identifier 7 and title River & rail. Request /notes/9 and expect the different title Mountain map. Changing the identifier must change the returned object.
Next request /notes?q=mountain. The response is an object containing an items array with note 9. A query with no match returns an empty array with status 200. It does not use 404 because the collection request itself is valid.
Encode query values with the client's URL utilities. The literal ampersand in River & rail must stay inside the q value. This fixture uses a simple case-insensitive substring search; it does not implement ranking, pagination, authentication or a formal search language.
Results retain the array's declared order. An empty q returns both notes, and searchParams.get uses the first q value when the URL repeats that name. Those choices are explicit teaching behavior, not recommendations for every API. Add a fixture assertion before changing either rule so that the client and service continue to agree on the query contract.
The example's lowercase comparison also does not promise locale-aware linguistic matching.
Observe the deliberately different failures
Request /notes/999 and expect a 404 error object. Request an unrelated path and observe the same status with this fixture's deliberately generic message. The client should distinguish that response from an empty items array.
Send POST to /notes and expect 405 with Allow: GET. No note is created. This teaches method handling while keeping the exercise read-only. The 405 reference explains the role of the Allow response field.
Choose a client appropriate to the local setup
A command-line client can call this loopback service directly. A browser application on another origin encounters browser sharing rules because this minimal server deliberately does not add CORS permission. Opening a URL for navigation and reading it through fetch are different browser operations.
The public REST API Testing Tool should therefore not be assumed to read this local HTTP fixture from its HTTPS origin. Use a local test client or extend a separate controlled fixture with a documented CORS policy. Do not disable browser security to make the exercise appear successful.
Reset and record the results
Stop the process with the terminal's interrupt action when finished. Restarting it restores the same two in-memory notes, which makes repeated tests predictable. Save each request's method, URL, status and parsed body rather than relying on a screenshot of one successful call.
For a complete exercise, check both note identifiers, one matching search, one empty search, one missing note and one unsupported method. Add assertions for exact identifiers and titles. That small matrix proves the advertised behavior without claiming that this teaching server is a production-ready API.


