Step 5: Read, update and delete
Now add the routes for a single to-do. A regular expression pulls the number out of /todos/3. Reading, changing (PATCH) and deleting (DELETE) all start by looking the to-do up, answering 404 if it is not there. A successful delete answers 204 No Content: success, with nothing to send back.
ExampleNode.js
const http = require("http");
const todos = [];
let nextId = 1;
function send(res, status, data) {
res.writeHead(status, { "Content-Type": "application/json" });
res.end(JSON.stringify(data));
}
function readBody(req) {
return new Promise((resolve, reject) => {
let raw = "";
req.on("data", (chunk) => (raw += chunk));
req.on("end", () => {
try {
resolve(raw ? JSON.parse(raw) : {});
} catch (err) {
reject(new Error("Invalid JSON"));
}
});
});
}
function validate(body) {
if (typeof body.title !== "string" || body.title.trim() === "") return "title is required";
if (body.title.length > 100) return "title must be 100 characters or fewer";
return null;
}
async function handle(req, res) {
try {
const match = req.url.match(/^\/todos\/(\d+)$/);
const id = match ? Number(match[1]) : null;
if (req.method === "GET" && req.url === "/todos") return send(res, 200, todos);
if (req.method === "POST" && req.url === "/todos") {
const body = await readBody(req);
const problem = validate(body);
if (problem) return send(res, 400, { error: problem });
const todo = { id: nextId++, title: body.title.trim(), done: false };
todos.push(todo);
return send(res, 201, todo);
}
if (id !== null) {
const todo = todos.find((t) => t.id === id);
if (!todo) return send(res, 404, { error: "Todo not found" });
if (req.method === "GET") return send(res, 200, todo);
if (req.method === "PATCH") {
const body = await readBody(req);
if (typeof body.done === "boolean") todo.done = body.done;
if (body.title !== undefined) {
const problem = validate(body);
if (problem) return send(res, 400, { error: problem });
todo.title = body.title.trim();
}
return send(res, 200, todo);
}
if (req.method === "DELETE") {
todos.splice(todos.indexOf(todo), 1);
res.writeHead(204);
return res.end();
}
}
send(res, 404, { error: "Not found" });
} catch (err) {
send(res, 400, { error: err.message });
}
}
// ---- demo: start on a free port, make a few requests, stop ----
const server = http.createServer(handle);
server.listen(0, async () => {
const base = `http://localhost:${server.address().port}`;
const call = async (method, path, body) => {
const res = await fetch(base + path, { method, body: body ? JSON.stringify(body) : undefined });
const text = await res.text();
console.log(method, path, "->", res.status, text || "(empty)");
};
await call("POST", "/todos", { title: "Write tests" });
await call("GET", "/todos/1");
await call("PATCH", "/todos/1", { done: true });
await call("PATCH", "/todos/1", { title: "" });
await call("GET", "/todos/99");
await call("DELETE", "/todos/1");
await call("GET", "/todos");
server.close();
});Output
POST /todos -> 201 {"id":1,"title":"Write tests","done":false}
GET /todos/1 -> 200 {"id":1,"title":"Write tests","done":false}
PATCH /todos/1 -> 200 {"id":1,"title":"Write tests","done":true}
PATCH /todos/1 -> 400 {"error":"title is required"}
GET /todos/99 -> 404 {"error":"Todo not found"}
DELETE /todos/1 -> 204 (empty)
GET /todos -> 200 []The API is now complete: create, read, update and delete (CRUD). The PATCH request changed only the field it contained, which is the difference between PATCH (partial change) and PUT (replace the whole item).
Quick check
What does the 204 status mean?
Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.
Tip: use the left and right arrow keys to move between lessons.