Files and paths
Build a notes command-line program
Combine arguments, JSON parsing, paths, and promise-based filesystem calls in one small Node.js program.
8 minute lesson
Build a program with add, list, and remove commands.
Define the command contract before the storage code:
node notes.js add "Buy milk"
node notes.js list
node notes.js remove <id>
Parse only the arguments each command accepts. Missing text, an invalid ID, or an unknown command is a user error: print a short usage message to standard error and set a non-zero exit status.
Store notes in a JSON file beside the ES module, not wherever the user happened to launch it:
const dataUrl = new URL('./notes.json', import.meta.url)
Treat only a missing file as an empty collection:
async function readNotes() {
try {
return JSON.parse(await readFile(dataUrl, 'utf8'))
} catch (error) {
if (error.code === 'ENOENT') return []
throw error
}
}
Invalid JSON, permission errors, and I/O failures must remain visible. Silently replacing corrupt storage with [] could make the next write erase recoverable notes.
Write a complete new snapshot to a temporary file beside the destination, flush and close it as required by your durability needs, then rename it over notes.json. Renaming on the same filesystem prevents readers from observing a half-written JSON document. Clean up a leftover temporary file after a failed write.
Atomic replacement is not the same as concurrency control. Two CLI processes can both read the old list and then each rename a different update; the last rename wins. Either document that only one process may modify the file, add an appropriate lock, or use a database when concurrent writers are a requirement.
Keep the command flow explicit:
- parse and validate the command
- load and validate the stored array
- calculate the new array without mutating partial state
- persist it safely
- print the result only after persistence succeeds
Use stable generated IDs rather than array indexes, because removing one note changes later indexes.
Return a non-zero exit status for an unknown command or invalid input.
Your action is to implement all three commands, then test a missing file, malformed JSON, invalid ID, failed write, and two simultaneous add commands. Document which concurrency guarantee your version actually provides.
Lesson completed