What is it?
JSON-Database-ST is a lightweight, promise-based database that uses a single JSON file for storage. It's designed for projects that need simple, persistent data storage without the overhead of a traditional database server. With a focus on security, performance, and developer experience, it includes features like atomic operations, data indexing, schema validation, and built-in encryption.
Key Features
🔒 Secure by Default
Built-in AES-256-GCM encryption at rest and path traversal protection to keep your data safe.
⚡ Fast Indexed Lookups
Create indexes on your data to retrieve records instantly (O(1)) instead of slow scanning.
🤝 Atomic Operations
All writes (set, push, batch, transaction) are atomic, ensuring data integrity even during concurrent operations.
✅ Schema Validation
Integrate with validation libraries like Zod or Joi to enforce data structures and prevent bad data.
🕊️ Modern Promise-Based API
A clean, async/await-friendly API that is intuitive and easy to use.
📢 Event-Driven
Emits events for 'write', 'change', and 'error', allowing for reactive programming and auditing.
Installation
# Required peer dependency
npm install lodash
npm install json-database-st
Quick Start
const JSONDatabase = require('./JSONDatabase');
const db = new JSONDatabase('./my-secure-db.json', {
// IMPORTANT: Store this key in environment variables, not in code!
encryptionKey: 'd0a7e8c1b2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9',
});
async function main() {
await db.set('users.alice', { name: 'Alice', age: 30, tags: ['active'] });
const alice = await db.get('users.alice');
console.log(alice); // -> { name: 'Alice', age: 30, tags: ['active'] }
await db.transaction(data => {
data.users.alice.age++;
return data;
});
console.log(await db.get('users.alice.age')); // -> 31
await db.close();
}
main();
API Reference
new JSONDatabase(filename, [options])
Creates a new database instance.
.get(path, [defaultValue])
Retrieves a value from the database using a lodash path. Returns the default value if the path does not exist.
const theme = await db.get('config.theme', 'light');
.set(path, value)
Atomically sets or replaces a value at a specific path.
await db.set('users.bob', { name: 'Bob', age: 40 });
.has(path)
Checks if a path exists in the database. Returns true even if the value at the path is `null` or `undefined`.
if (await db.has('users.bob')) {
console.log('Bob exists!');
}
.delete(path)
Atomically deletes a property at a specified path. Returns `true` if the property existed and was deleted.
const wasDeleted = await db.delete('users.temporary');
if (wasDeleted) console.log('Temporary user cleaned up.');
.push(path, ...items)
Pushes one or more unique items into an array at a given path. Creates the array if it doesn't exist. Uses deep comparison for uniqueness.
await db.push('users.alice.tags', 'verified', 'premium');
.pull(path, ...itemsToRemove)
Removes one or more items from an array at a given path. Uses deep comparison to find items to remove.
await db.pull('users.alice.tags', 'active');
.transaction(asyncFn)
Performs a complex, multi-step operation atomically. The provided function receives a deep clone of the data and MUST return the modified data object.
await db.transaction(async (data) => {
data.users.alice.logins = (data.users.alice.logins || 0) + 1;
data.users.alice.lastLogin = Date.now();
return data; // IMPORTANT: must return the data
});
.batch(ops, [options])
Executes multiple simple operations atomically in a single disk write for high performance.
await db.batch([
{ type: 'set', path: 'users.jane', value: { age: 30 } },
{ type: 'push', path: 'users.jane.hobbies', values: ['reading'] },
{ type: 'delete', path: 'users.oldUser' }
]);
.find(collectionPath, predicate)
Finds the first entry in a collection (an object or array) that satisfies the predicate function.
const adminUser = await db.find('users', (user, key) => {
return user.tags.includes('admin');
});
.findByIndex(indexName, value)
Instantly finds an object in a collection using a pre-configured index. This is the fastest way to look up data.
Requires setup in constructor:
const db = new JSONDatabase('db', {
indices: [{ name: 'user-email', path: 'users', field: 'email', unique: true }]
});
// Later...
const user = await db.findByIndex('user-email', 'alice@example.com');
.clear()
Clears the entire database content, replacing it with an empty object (`{}`). Use with caution!
await db.clear();
console.log('Database has been reset.');
.getStats()
Returns a synchronous object containing operational statistics for the database instance.
const stats = db.getStats();
console.log(`DB writes: ${stats.writes}, DB reads: ${stats.reads}`);
.close()
Waits for any pending write operations to complete, then closes the database instance. Call this for a graceful shutdown.
await db.close();
console.log('Database connection closed safely.');