11. Mini project: a CLI todo app (Node)
// todo.js — node todo.js add "Buy milk" | node todo.js list | node todo.js done 0
import { readFileSync, writeFileSync, existsSync } from "node:fs";
const DB_PATH = "./todos.json";
function loadTodos() {
return existsSync(DB_PATH) ? JSON.parse(readFileSync(DB_PATH, "utf8")) : [];
}
function saveTodos(todos) {
writeFileSync(DB_PATH, JSON.stringify(todos, null, 2));
}
const [, , command, ...args] = process.argv;
const todos = loadTodos();
switch (command) {
case "add":
todos.push({ text: args.join(" "), done: false });
saveTodos(todos);
console.log(`Added: "${args.join(" ")}"`);
break;
case "list":
todos.forEach((t, i) => console.log(`${i}: [${t.done ? "x" : " "}] ${t.text}`));
break;
case "done":
const idx = Number(args[0]);
if (todos[idx]) { todos[idx].done = true; saveTodos(todos); console.log("Marked done."); }
break;
default:
console.log("Usage: todo.js <add|list|done> [args]");
}
This exercises closures-free module state (a file as the "database"), array methods, template literals, and basic error-free control flow — the full Part I toolkit.