Reading Files
One of Node.js's most powerful features is its ability to work with the file system. The fs (file system) module lets you read, write, and manipulate files and directories. In this lesson, you'll learn different ways to read files.
The fs Module
The fs module is built into Node.js:
const fs = require('fs');
// Or with ES Modules
import fs from 'fs';
import fs from 'node:fs'; // With node: prefix
The module provides three styles of APIs:
- Synchronous (blocking) -
readFileSync - Callback-based (async) -
readFile - Promise-based (async) -
fs/promises
Reading Files Synchronously
The simplest approach—the code waits until the file is read:
const fs = require('fs');
// Read entire file as string
const content = fs.readFileSync('example.txt', 'utf8');
console.log(content);
// Read as Buffer (binary data)
const buffer = fs.readFileSync('image.png');
console.log(buffer); // <Buffer 89 50 4e 47 ...>
When to Use Sync Methods
- During application startup (reading config files)
- In scripts that run once
- When you need the result before continuing
When NOT to Use Sync Methods
- In web servers handling requests
- In event handlers
- Anywhere performance matters
Reading Files with Callbacks
The traditional async approach using callbacks:
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err.message);
return;
}
console.log('File contents:', data);
});
console.log('This runs FIRST!'); // Non-blocking
The callback pattern:
- First parameter is always the error (or null)
- Second parameter is the result
- Code continues executing while file is being read
Reading Files with Promises
The modern approach using fs/promises:
const fs = require('fs/promises');
// Using async/await
async function readConfig() {
try {
const content = await fs.readFile('config.json', 'utf8');
const config = JSON.parse(content);
console.log('Config loaded:', config);
return config;
} catch (err) {
console.error('Failed to read config:', err.message);
throw err;
}
}
readConfig();
// Using .then()
fs.readFile('data.txt', 'utf8')
.then(data => console.log('Data:', data))
.catch(err => console.error('Error:', err.message));
File Encodings
The encoding parameter determines how the file is read:
| Encoding | Description | Use Case |
|---|---|---|
'utf8' | Text (Unicode) | Most text files |
'ascii' | ASCII text | Simple text |
'base64' | Base64 encoding | Encoded data |
'hex' | Hexadecimal | Binary as hex |
null | Raw Buffer | Binary files |
// Text file - specify encoding
const text = fs.readFileSync('readme.txt', 'utf8');
console.log(typeof text); // 'string'
// Binary file - no encoding
const binary = fs.readFileSync('image.png');
console.log(Buffer.isBuffer(binary)); // true
// Convert Buffer to string later
const buffer = fs.readFileSync('file.txt');
const string = buffer.toString('utf8');
Checking If a File Exists
Before reading, you might want to check if a file exists:
const fs = require('fs');
// Synchronous
if (fs.existsSync('config.json')) {
const config = fs.readFileSync('config.json', 'utf8');
console.log('Config loaded');
} else {
console.log('Config file not found, using defaults');
}
// Async - use try/catch instead
const fsPromises = require('fs/promises');
async function readOptionalFile(path) {
try {
return await fsPromises.readFile(path, 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
return null; // File doesn't exist
}
throw err; // Other errors
}
}
Reading Large Files with Streams
For large files, streams read data in chunks:
const fs = require('fs');
const stream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 1024 // Read 1KB at a time
});
stream.on('data', (chunk) => {
console.log('Received chunk:', chunk.length, 'bytes');
});
stream.on('end', () => {
console.log('Finished reading file');
});
stream.on('error', (err) => {
console.error('Error:', err.message);
});
Practical Exercise
Common Error Codes
| Error Code | Meaning |
|---|---|
ENOENT | No such file or directory |
EACCES | Permission denied |
EISDIR | Expected file but got directory |
EMFILE | Too many open files |
ENOTDIR | Not a directory |
fs.readFile('missing.txt', 'utf8', (err, data) => {
if (err) {
switch (err.code) {
case 'ENOENT':
console.log('File does not exist');
break;
case 'EACCES':
console.log('No permission to read file');
break;
default:
console.log('Unknown error:', err.message);
}
return;
}
console.log(data);
});
Key Takeaways
- Use
fs.readFileSync()for blocking reads (startup, scripts) - Use
fs.readFile()with callbacks for async reads - Use
fs/promiseswith async/await for modern async reads - Specify
'utf8'encoding for text files - Omit encoding for binary files (returns Buffer)
- Use streams for large files
- Always handle errors (ENOENT, EACCES, etc.)
Summary
You've learned multiple ways to read files in Node.js: synchronous for simple cases, callbacks for traditional async code, and promises for modern async/await patterns. Understanding when to use each approach is key to writing efficient Node.js applications. For most server-side code, the promise-based approach with async/await is recommended.
Next, we'll learn how to write files and save data to the file system.

