Step 4: Validate input and handle errors
Never trust input. Before saving, check that title is a non-empty string of a sensible length, and answer 400 Bad Request with a clear message if not. Wrap the route code in try/catch so malformed JSON becomes a 400 instead of a crash.
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 {
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);
}
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: " " });
await call("POST", "/todos", {});
await call("POST", "/todos", { title: "x".repeat(101) });
const res = await fetch(base + "/todos", { method: "POST", body: "{not json" });
console.log("POST /todos (bad JSON) ->", res.status, await res.text());
await call("POST", "/todos", { title: " Valid title " });
server.close();
});Output
POST /todos -> 400 {"error":"title is required"}
POST /todos -> 400 {"error":"title is required"}
POST /todos -> 400 {"error":"title must be 100 characters or fewer"}
POST /todos (bad JSON) -> 400 {"error":"Invalid JSON"}
POST /todos -> 201 {"id":1,"title":"Valid title","done":false}Every bad request gets a helpful 400, and the valid one is saved with the title trimmed. Good error messages are a big part of a good API.
Quick check
Which status code means the client sent bad input?
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.