Zero-config automatic JSON file persistence with atomic writes, debouncing, and crash safety. Just modify objects—changes save automatically!
Seamlessly sync JavaScript values to JSON files in the background. Changes are automatically detected and saved with enterprise-grade reliability features like atomic writes and debouncing. Perfect for config files, application state, caches, or simple embedded databases.
- 🚀 Zero Configuration - Just modify objects, saves happen automatically
- 🛡️ Crash-Safe Atomic Writes - No corrupted files, even during power failures
- ⚡ Smart Debouncing - Efficient disk I/O with configurable delays
- đź’ľ Multiple API Styles - Callbacks, Promises, or async/await
- 🎯 TypeScript Ready - Full type definitions included
- 📦 Tiny Footprint - Zero dependencies, pure Node.js
- âś… Well Tested - 92% test coverage with 41 comprehensive tests
- Node.js 6.0.0 or higher (Node.js 14+ recommended for async/await support)
- Native support for ES6 Proxy and Reflect
Proxy support is key to filejson's elegant API. Unlike other modules that require explicit function calls for each save operation, filejson automatically detects and saves changes:
// Other modules require explicit save calls
otherModule.set("key", "value");
otherModule.save();
// filejson saves automatically - just modify the object
file.contents.key = "value"; // ✨ Automatically saved!| Node.js Version | Status | Features Available |
|---|---|---|
| 18+ (Current LTS) | âś… Recommended | All features, best performance |
| 14+ | âś… Supported | Async/await, Promises, all features |
| 12+ | âś… Supported | Async/await, Promises, all features |
| 6-11 | Callbacks only, limited testing | |
| < 6 | ❌ Not Supported | Requires polyfills (see below) |
For Node.js versions older than 6, you'll need additional setup. See the legacy installation guide below.
npm install filejson --saveThis package includes a comprehensive test suite using Mocha.
# Run all tests
npm test
# Run tests in watch mode
npm run test:watchThis package uses ESLint for code quality and consistency.
# Run ESLint
npm run lint
# Auto-fix ESLint issues
npm run lint:fixconst Filejson = require("filejson");
try {
const file1 = new Filejson();
file1.load("file1.json", function (error, file) {
if (error) {
console.error("Load error:", error);
return;
}
console.log(file.contents); // outputs {"abc": "123"}
file.contents.msg = "Hello World"; // saves {"abc": "123", "msg": "Hello World"}
console.log(file.contents); // outputs {"abc": "123", "msg": "Hello World"}
});
} catch (error) {
console.error("Initialization error:", error);
}const Filejson = require("filejson");
try {
const file1 = new Filejson();
file1
.load("file1.json")
.then((file) => {
console.log(file.contents); // outputs {"abc": "123"}
file.contents.msg = "Hello World"; // saves {"abc": "123", "msg": "Hello World"}
console.log(file.contents); // outputs {"abc": "123", "msg": "Hello World"}
})
.catch((error) => {
console.error("Error:", error);
});
} catch (error) {
console.error("Initialization error:", error);
}const Filejson = require("filejson");
try {
const file1 = new Filejson();
const file = await file1.load("file1.json"); // file1.json contains {"abc": "123"}
console.log(file.contents); // outputs {"abc": "123"}
file.contents.msg = "Hello World"; // Automatically saved!
console.log(file.contents); // outputs {"abc": "123", "msg": "Hello World"}
} catch (error) {
console.error("Error:", error);
}Note: Top-level
awaitrequires Node.js >= 14.8. If using an older version, wrap the code in an async function.
new Filejson([config]);Parameters:
config(Object, optional): Configuration optionsfilename(string): Default filename to use (can be overridden inload())space(number): Number of spaces for JSON indentation (default: 2)verbose(boolean): Enable verbose logging (default: false)saveDelay(number): Milliseconds to wait before saving after a change (default: 100ms)atomicWrites(boolean): Enable atomic writes for crash safety (default: true)
Example:
const file = new Filejson({ space: 4, verbose: true, saveDelay: 100 });Loads a JSON file and sets up automatic syncing.
Parameters:
filename(string, required): Path to the JSON fileoverwriteWith(any, optional): Initial data to overwrite the file withcallback(function, optional): Callback function(error, instance) => {}
Returns: Promise if no callback provided, undefined otherwise
Examples:
// Callback style
file.load("data.json", (error, instance) => {
/* ... */
});
// Promise style
file.load("data.json").then((instance) => {
/* ... */
});
// Async/await style
const instance = await file.load("data.json");
// With initial data
await file.load("data.json", { initial: "data" });Manually saves the current contents to disk. Note: saves happen automatically when you modify contents.
Parameters:
callback(function, optional): Callback function(error, instance) => {}
Returns: Promise if no callback provided, undefined otherwise
Examples:
// Callback style
file.save((error, instance) => {
/* ... */
});
// Promise style
file.save().then((instance) => {
/* ... */
});
// Async/await style
await file.save();Synchronously saves the current contents to disk. Unlike save(), this method blocks until the write completes and does not return a Promise.
Primary Use Case: Exit handlers where async operations may not complete before process termination.
Parameters: None
Returns: The Filejson instance (for method chaining)
Throws: Error if serialization or file write fails
Examples:
// In an exit handler
process.on('SIGINT', () => {
console.log('Saving before exit...');
file.saveSync(); // Blocks until saved
process.exit(0);
});
// With error handling
process.on('SIGTERM', () => {
try {
file.saveSync();
console.log('Saved successfully');
} catch (error) {
console.error('Save failed:', error);
}
process.exit(0);
});
// Method chaining
file.saveSync().paused = false;Important Notes:
- Clears any pending debounced saves
- Still respects
atomicWritesconfiguration - Blocking operation - use
save()for normal operations - Only use in exit handlers or when sync behavior is required
The JavaScript value that is automatically synced to the JSON file. Can be modified directly, and changes are automatically saved.
Example:
await file.load("data.json");
// Simple modifications
file.contents.name = "John";
file.contents.age = 30;
// Nested modifications
file.contents.preferences.theme = "dark";
// Array modifications
file.contents.tags.push("new-tag");
// Deletions
delete file.contents.obsoleteField;
// All of the above trigger automatic saves!Boolean flag to temporarily pause automatic saving. Useful when making multiple changes and you want to save only once.
Example:
file.paused = true;
file.contents.field1 = "value1";
file.contents.field2 = "value2";
file.contents.field3 = "value3";
file.paused = false;
await file.save(); // Save all changes at onceYou typically don't need to call these methods manually - that's the whole point of filejson! Changes are automatically saved in the background.
However, there are a few edge cases where manual saves are useful:
Ensure all changes are written before your application closes. Always use saveSync() because async operations may not complete before the process exits:
process.on("SIGINT", () => {
file.saveSync(); // Synchronous - blocks until saved
process.exit(0);
});
process.on("SIGTERM", () => {
try {
file.saveSync();
} catch (error) {
console.error("Failed to save on exit:", error);
}
process.exit(1);
});Explicitly save after making batched changes:
file.paused = true;
// ... make many changes ...
file.paused = false;
await file.save();Force immediate write despite saveDelay setting:
const file = new Filejson({ saveDelay: 500 });
file.contents.critical = "urgent data";
await file.save(); // Writes immediately, bypassing 500ms delayEnsure data is written synchronously before another process accesses it:
file.contents.config = newSettings;
file.saveSync(); // Blocks until written to disk
exec("other-program --config data.json"); // Now safe to useRemember: For normal usage, just modify
file.contentsand let filejson handle the saving automatically!
By default, filejson uses a 100ms debounce delay to optimize disk I/O. You can adjust this value based on your needs, or set it to 0 for immediate saves (no debouncing).
- Reduce Disk I/O: When making many rapid changes, debouncing ensures only one write occurs after changes stop
- Improve Performance: Fewer disk writes mean better performance, especially on slower storage
- Batch Updates: Automatically combines multiple rapid changes into a single save operation
// Configure a 100ms debounce delay
const file = new Filejson({ saveDelay: 100 });
await file.load("data.json");
// Make rapid changes - only saves once after 100ms of inactivity
for (let i = 0; i < 1000; i++) {
file.contents.counter = i;
}
// Only one file write occurs 100ms after the loop completesImmediate saves (no debouncing):
const file = new Filejson({ saveDelay: 0 });
await file.load("data.json");
file.contents.value = 1; // Saves immediately
file.contents.value = 2; // Saves immediately
// Result: 2 disk writesDebounced saves:
const file = new Filejson({ saveDelay: 100 });
await file.load("data.json");
file.contents.value = 1; // Scheduled to save in 100ms
file.contents.value = 2; // Cancels previous, schedules new save in 100ms
// Result: 1 disk write (after 100ms of inactivity)Manual save bypasses debounce:
const file = new Filejson({ saveDelay: 500 });
await file.load("data.json");
file.contents.value = 1;
await file.save(); // Saves immediately, bypassing the 500ms delay0ms(default): Immediate saves, no debouncing - best for infrequent updates50-100ms: Good balance for most applications with moderate update frequency200-500ms: Suitable for high-frequency updates (e.g., real-time data)1000ms+: For very high-frequency updates where data loss tolerance is acceptable
By default, filejson uses atomic writes to ensure your data files are never corrupted, even if your application crashes or loses power during a save operation.
- Write to a temporary file (e.g.,
data.json.tmp12345) - Atomically rename the temp file to the target filename (e.g.,
data.json) - The rename operation is atomic on most file systems - other processes see either the old complete file or the new complete file, never a partially written file
- No Data Corruption: Prevents partial writes if your process crashes mid-save
- Crash Safety: If power fails during write, your original file remains intact
- Consistency: Other processes never see incomplete or malformed JSON
- Production Ready: Safe for use in production environments with critical data
Atomic writes are enabled by default and require no configuration:
const file = new Filejson();
await file.load("data.json");
file.contents.critical = "data";
// Saves atomically - guaranteed no corruption even if process crashesIn some edge cases (e.g., network file systems with slow renames), you may want to disable atomic writes:
const file = new Filejson({ atomicWrites: false });
await file.load("data.json");
file.contents.value = "data";
// Writes directly to the file (faster but not crash-safe)- Network file systems where rename operations are slow or problematic
- Non-critical data where corruption risk is acceptable for performance
- Testing environments where you want to observe partial write behavior
Note: For most use cases, keep atomic writes enabled (the default) for maximum safety.
- You will need a polyfill such as harmony-reflect:
npm install harmony-reflect --save
- In addition to requiring filejson, you will need to require harmony-reflect at the top of your app, like this:
var Reflect = require("harmony-reflect");- Lastly, every time you run your app you will need to use the node --harmony_proxies flag, like this:
node --harmony_proxies index.js