jsonfile, lowdb, and write-json-file address different layers of JSON file management in Node.js. jsonfile is a dedicated utility for reading and writing JSON files with built-in error handling and formatting options. lowdb is a lightweight local database that uses JSON files as storage, providing a simple API for querying and modifying data. write-json-file focuses specifically on the atomic writing of JSON data to files, ensuring data integrity during write operations. Each serves a distinct purpose: direct file I/O, in-memory database functionality, and safe file writing respectively.
When working with JSON data in Node.js applications, developers often face the choice between direct file manipulation, lightweight database solutions, and atomic write operations. The packages jsonfile, lowdb, and write-json-file each solve different problems in this space. Let's examine how they approach common challenges.
jsonfile provides straightforward methods for reading and writing JSON files with automatic parsing and stringification.
const jsonfile = require('jsonfile');
// Reading JSON
const obj = await jsonfile.readFile('./data.json');
console.log(obj.name);
// Writing JSON with formatting
await jsonfile.writeFile('./data.json', { name: 'John' }, { spaces: 2 });
lowdb reads and writes JSON through its database instance, treating the file as a persistent data store.
const { Low } = require('lowdb');
const { JSONFile } = require('lowdb/node');
const db = new Low(new JSONFile('data.json'));
await db.read();
// Access data
console.log(db.data.name);
// Write data
db.data.name = 'John';
await db.write();
write-json-file focuses exclusively on writing JSON data safely to files.
const writeJsonFile = require('write-json-file');
// Atomic write operation
await writeJsonFile('./data.json', { name: 'John' }, { indent: 2 });
// Note: No built-in reading capability - pair with fs.readFile or jsonfile
jsonfile performs standard write operations that can potentially leave corrupted files if the process crashes mid-write.
// Standard write - vulnerable to interruption
await jsonfile.writeFile('./config.json', config);
// If process crashes here, file may be corrupted
lowdb inherits the same vulnerability as it uses standard file writing internally.
// Database write - also vulnerable to interruption
db.data.users.push(newUser);
await db.write();
// Crash during write could corrupt the entire database file
write-json-file implements atomic writes by writing to a temporary file first, then renaming it.
// Atomic write - safe from interruptions
await writeJsonFile('./config.json', config);
// Writes to temp file first, then renames - never leaves partial data
jsonfile requires manual data manipulation after reading - no built-in querying.
const data = await jsonfile.readFile('./users.json');
// Manual filtering
const activeUsers = data.users.filter(u => u.active);
const user = data.users.find(u => u.id === 123);
lowdb provides lodash-based chaining for querying and modifying data directly.
const activeUsers = db.get('users')
.filter({ active: true })
.value();
const user = db.get('users')
.find({ id: 123 })
.value();
// Direct modification
db.get('users').push({ id: 124, name: 'Jane' }).write();
write-json-file offers no querying capabilities - it's write-only.
// Must manage data structure externally
const data = { users: [...] }; // Managed in memory
await writeJsonFile('./users.json', data);
jsonfile loads entire files into memory for each operation, suitable for small to medium files.
// Entire file loaded into memory
const largeData = await jsonfile.readFile('./large-dataset.json');
// Memory usage scales with file size
lowdb keeps data in memory after initial load, providing fast access but requiring full file reloads on write.
// Initial load into memory
await db.read();
// Fast in-memory operations
const result = db.get('largeArray').find({ id: 999 }).value();
// Full file rewrite on any change
await db.write();
write-json-file optimizes only the write path with atomic operations, but still requires full data serialization.
// Efficient atomic write, but still serializes entire object
await writeJsonFile('./state.json', largeStateObject);
jsonfile handles common edge cases like missing directories and provides detailed error messages.
try {
const data = await jsonfile.readFile('./missing.json');
} catch (err) {
// Clear error handling
console.error('File not found or invalid JSON:', err.message);
}
// Auto-creates directories
await jsonfile.writeFile('./nested/dir/file.json', data, { mkdirp: true });
lowdb provides basic error handling but requires manual setup for missing files.
try {
await db.read();
} catch (err) {
// Handle missing or corrupted database
db.data = { users: [] }; // Reset to default
await db.write();
}
write-json-file focuses on write-time errors and ensures cleanup of temporary files.
try {
await writeJsonFile('./critical.json', importantData);
} catch (err) {
// Write failed, but no partial data left behind
console.error('Atomic write failed:', err.message);
}
For application configuration files that need reliable updates:
// Using write-json-file for atomic config updates
const writeJsonFile = require('write-json-file');
async function updateConfig(newSettings) {
const currentConfig = await jsonfile.readFile('./config.json');
const updatedConfig = { ...currentConfig, ...newSettings };
await writeJsonFile('./config.json', updatedConfig);
}
For prototyping and development with simple querying needs:
// Using lowdb for local data store
const { Low } = require('lowdb');
const { JSONFile } = require('lowdb/node');
const db = new Low(new JSONFile('dev-data.json'));
await db.read();
// Easy querying during development
const activeUsers = db.get('users').filter({ status: 'active' }).value();
For straightforward JSON file operations without database overhead:
// Using jsonfile for simple save/load operations
const jsonfile = require('jsonfile');
async function saveUserPreferences(userId, prefs) {
const file = `./users/${userId}-prefs.json`;
await jsonfile.writeFile(file, prefs, { spaces: 2 });
}
async function loadUserPreferences(userId) {
const file = `./users/${userId}-prefs.json`;
return await jsonfile.readFile(file);
}
| Feature | jsonfile | lowdb | write-json-file |
|---|---|---|---|
| Read Support | ✅ Full | ✅ Full | ❌ None |
| Write Support | ✅ Standard | ✅ Standard | ✅ Atomic |
| Querying | ❌ Manual | ✅ Lodash chaining | ❌ None |
| Data Integrity | ⚠️ Standard writes | ⚠️ Standard writes | ✅ Atomic operations |
| Memory Usage | Per-operation load | Persistent in-memory | Per-operation load |
| Best For | General JSON I/O | Local database | Critical writes |
jsonfile is your go-to for general JSON file operations - think configuration files, simple data persistence, or any scenario where you need reliable read/write capabilities without database complexity. It's the Swiss Army knife of JSON file handling.
lowdb shines when you need a lightweight, file-based database with querying capabilities. Perfect for development tools, CLI applications, or prototypes where you want database-like operations without the setup overhead. Just remember its limitations with concurrent access and large datasets.
write-json-file is specialized for scenarios where data integrity during writes is non-negotiable. Use it for critical configuration updates, state persistence in production environments, or any situation where file corruption during writes could cause serious problems.
Pro Tip: These packages aren't mutually exclusive. Many production applications use jsonfile for general operations, write-json-file for critical writes, and avoid lowdb in favor of proper databases for anything beyond development or simple tools.
Choose based on your specific needs: general I/O (jsonfile), querying capabilities (lowdb), or write safety (write-json-file). Understanding these distinctions helps you build more reliable and maintainable Node.js applications.
Choose jsonfile when you need straightforward, reliable reading and writing of JSON files with minimal setup. It's ideal for configuration files, simple data persistence, or any scenario where you need to serialize/deserialize JSON to/from disk without database overhead. The package handles common edge cases like directory creation and provides both synchronous and asynchronous APIs.
Choose lowdb when you need a simple, file-based database for small to medium datasets with query capabilities. It's perfect for prototyping, local development, CLI tools, or applications that need to store structured data with basic filtering and chaining operations. Avoid it for high-concurrency scenarios or large datasets where performance becomes critical.
Choose write-json-file when data integrity during write operations is paramount, such as in production environments where partial writes could corrupt critical data. It's specifically designed for atomic writes using temporary files and renaming, making it suitable for configuration management, state persistence, or any scenario where file corruption during writes must be prevented.
Easily read/write JSON files in Node.js. Note: this module cannot be used in the browser.
Writing JSON.stringify() and then fs.writeFile() and JSON.parse() with fs.readFile() enclosed in try/catch blocks became annoying.
npm install --save jsonfile
readFile(filename, [options], callback)readFileSync(filename, [options])writeFile(filename, obj, [options], callback)writeFileSync(filename, obj, [options])options (object, default undefined): Pass in any fs.readFile options or set reviver for a JSON reviver.
throws (boolean, default: true). If JSON.parse throws an error, pass this error to the callback.
If false, returns null for the object.const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
jsonfile.readFile(file, function (err, obj) {
if (err) console.error(err)
console.dir(obj)
})
You can also use this method with promises. The readFile method will return a promise if you do not pass a callback function.
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
jsonfile.readFile(file)
.then(obj => console.dir(obj))
.catch(error => console.error(error))
options (object, default undefined): Pass in any fs.readFileSync options or set reviver for a JSON reviver.
throws (boolean, default: true). If an error is encountered reading or parsing the file, throw the error. If false, returns null for the object.const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
console.dir(jsonfile.readFileSync(file))
options: Pass in any fs.writeFile options or set replacer for a JSON replacer. Can also pass in spaces, or override EOL string or set finalEOL flag as false to not save the file with EOL at the end.
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFile(file, obj, function (err) {
if (err) console.error(err)
})
Or use with promises as follows:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFile(file, obj)
.then(res => {
console.log('Write complete')
})
.catch(error => console.error(error))
formatting with spaces:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFile(file, obj, { spaces: 2 }, function (err) {
if (err) console.error(err)
})
overriding EOL:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFile(file, obj, { spaces: 2, EOL: '\r\n' }, function (err) {
if (err) console.error(err)
})
disabling the EOL at the end of file:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFile(file, obj, { spaces: 2, finalEOL: false }, function (err) {
if (err) console.log(err)
})
appending to an existing JSON file:
You can use fs.writeFile option { flag: 'a' } to achieve this.
const jsonfile = require('jsonfile')
const file = '/tmp/mayAlreadyExistedData.json'
const obj = { name: 'JP' }
jsonfile.writeFile(file, obj, { flag: 'a' }, function (err) {
if (err) console.error(err)
})
options: Pass in any fs.writeFileSync options or set replacer for a JSON replacer. Can also pass in spaces, or override EOL string or set finalEOL flag as false to not save the file with EOL at the end.
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFileSync(file, obj)
formatting with spaces:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFileSync(file, obj, { spaces: 2 })
overriding EOL:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFileSync(file, obj, { spaces: 2, EOL: '\r\n' })
disabling the EOL at the end of file:
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }
jsonfile.writeFileSync(file, obj, { spaces: 2, finalEOL: false })
appending to an existing JSON file:
You can use fs.writeFileSync option { flag: 'a' } to achieve this.
const jsonfile = require('jsonfile')
const file = '/tmp/mayAlreadyExistedData.json'
const obj = { name: 'JP' }
jsonfile.writeFileSync(file, obj, { flag: 'a' })
(MIT License)
Copyright 2012-2016, JP Richardson jprichardson@gmail.com