JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
146: Reading and Writing Files in Node
Up until now, we've been writing scripts that live and die in the terminal. Once you stop the process, your data vanishes. To build anything actually useful—like a CLI tool or a simple server—you need a way to persist data. In Node, that means getting comfortable with the fs (File System) module.
I prefer using the promises-based API over the old callback style. Callbacks lead to "callback hell" pretty quickly, and since we've already covered async/await, it's the only way to go for clean, readable code.
Setting up our Activity Logger
Let's build a simple activity logger. The goal is a script where we can pass a message, and it appends that message along with a timestamp to a file called log.txt. If the file doesn't exist, Node should just create it for us.
const fs = require('fs/promises');
async function logActivity(message) {
const timestamp = new Date().toISOString();
const entry = `[${timestamp}] ${message}\n`;
try {
await fs.appendFile('log.txt', entry);
console.log('Logged successfully!');
} catch (err) {
console.error('Something went wrong writing to the file:', err);
}
}
logActivity('User logged in');
logActivity('Updated profile settings');
I used appendFile here instead of writeFile. If I had used writeFile, it would overwrite the entire file every time I called the function, and I'd only ever have the very last log entry. Not exactly helpful for a log.
Reading the log back into the console
Now that we're saving data, we need to be able to read it. I'll add a function to our script that reads the entire log.txt file and prints it to the terminal so we can verify our entries.
async function readLogs() {
try {
const data = await fs.readFile('log.txt');
console.log('--- Current Logs ---');
console.log(data);
} catch (err) {
console.error('Could not read the file:', err);
}
}
readLogs();
Wait, why am I seeing numbers?
If you run the code above, you'll notice something weird. Instead of seeing your text logs, you'll see something like <Buffer 5b 32 30 32 34...>. This is a classic "gotcha" in Node.js.
By default, fs.readFile returns a Buffer—a raw sequence of bytes. Node doesn't assume you want a UTF-8 string because you might be reading an image, a PDF, or some other binary file. I forgot to specify the encoding, which is a mistake I still make occasionally when I'm rushing.
To fix this, we just need to pass 'utf8' as the second argument. This tells Node to decode those bytes into a human-readable string.
async function readLogs() {
try {
// Adding 'utf8' here is the magic fix
const data = await fs.readFile('log.txt', 'utf8');
console.log('--- Current Logs ---');
console.log(data);
} catch (err) {
console.error('Could not read the file:', err);
}
}
Wrapping it into a usable utility
To make this feel like a real tool, let's allow it to take arguments from the command line. We can use process.argv to decide whether we want to write a new log or read the existing ones.
async function main() {
const [,, command, ...args] = process.argv;
if (command === 'add') {
const message = args.join(' ');
await logActivity(message);
} else if (command === 'read') {
await readLogs();
} else {
console.log('Usage: node logger.js [add "message" | read]');
}
}
main();
Now you can run node logger.js add "Finished the FS lesson" to save a note, or node logger.js read to see everything you've tracked. It's simple, but this pattern of reading and writing is the foundation for almost every backend application.
📋 Practical Task
Build a JSON Configuration Updater
Create a script that manages a config.json file. Your script should perform the following steps:
- Check if
config.jsonexists. If it doesn't, create it with a default object:{ "theme": "light", "notifications": true }. - Read the current contents of
config.json. - Change the
themevalue to"dark". - Write the updated object back to the file.
Requirement: Use fs/promises and ensure you use JSON.parse() and JSON.stringify() to handle the data conversion between the file's string format and JavaScript objects.
There are no comments for now.