davidb
v1.0.4
Published
Lightweight, MongoDB-like JSON database for Node.js
Maintainers
Readme
daviDB
A lightweight, MongoDB-like JSON database for Node.js and browser JS (with optional HTTP/REST API mode).
Key Features
- Parallel operations with per-collection locking
- Intelligent LRU caching — no eviction of actively used collections
- Atomic updates with automatic persistence
- MongoDB-like query syntax —
$gt,$in,$or,$regex, etc. - Batch operations for insert, update, and delete
- Real-time events — subscribe to database changes
- Lightweight — zero external dependencies
- Debug mode for easy troubleshooting
Table of Contents
- Introduction
- Installation
- Configuration
- Insert Documents
- Find Documents
- Update Documents
- Count Documents
- Delete Documents
- Filters & Operators
- Pagination
- Field Projection
- Database Management
- Events
- Error Handling
- Acknowledgments
Introduction
daviDB is a fast, lightweight JSON database inspired by MongoDB. It supports two operation modes:
- Local Mode — Store data in JSON files on your filesystem (Node.js only)
- HTTP Mode — Connect to a remote daviDB server using an HTTP client
Note: Local mode is only available in Node.js. Browser environments require HTTP mode with a token.
HTTPAdapter is currently unavailable. HTTP mode (remote server connections, browser usage) is not functional at this time. Only Local Mode is supported in the current version.
Installation
NPM
npm install daviDBYarn
yarn add daviDBBasic Usage
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.findById(1)
.then((user) => {
if (!user) return console.error('User not found');
console.log('User found: ', user);
})
.catch(e => console.error('Error while finding user: ', e));Configuration
Local Mode (Node.js)
const db = new daviDB({
storagePath: './storage', // Storage directory
autoSaveInterval: 5000, // Auto-save every 5s
debug: true // Enable detailed logs
});Local Mode Options
| Option | Type | Default | Description |
|---|---|---|---|
| debug | boolean | true | Enable detailed logging |
| enableEvents | boolean | true | Enable events |
| storagePath | string | './storage' | Directory for JSON files (Local mode) |
| autoSaveInterval | number | 5000 | Auto-save interval in milliseconds |
| maxDocumentsReturn | number | 100 | Max documents to return when using find/update methods |
| maxDocumentsInsert | number | 100 | Max documents to insert when using insertMany method |
| maxEventDocuments | number | 100 | Max documents to include in events |
| maxCollectionsCacheSize | number | 20 | Max collections in cache |
| autoShutdown | boolean | true | Auto-shutdown (flush & close) when received SIGINT signal |
HTTP Mode (Client)
HTTPAdapter is currently unavailable. The configuration below is documented for reference but is not functional in the current version.
const db = new daviDB({
url: 'http://127.0.0.1:8050',
token: 'your-auth-token', // Required for HTTP mode
poolSize: 10, // Connection pool size
timeout: 15000, // Request timeout (ms)
debug: true
});HTTP Mode Options
| Option | Type | Default | Description |
|---|---|---|---|
| debug | boolean | false | Enable detailed logging |
| url | string | - | Remote database URL (HTTP mode) |
| token | string | - | Authentication token (HTTP mode) |
| poolSize | number | 10 | HTTP connection pool size |
| timeout | number | 15000 | Request timeout in milliseconds |
Insert Documents
Add new documents to a collection. Supports single or bulk insertion with automatic ID generation.
Documents automatically receive
id,createdAt, andupdatedAtfields. If anidis provided, daviDB will use it. Otherwise, it will generate a random one.
Insert Single Document
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.insertOne({
id: 1,
username: 'alice',
email: '[email protected]',
age: 28
})
.then((user) => {
if (!user) return console.error('User with this id already exists');
console.log('Inserted user: ', user);
})
.catch(e => console.error('Error while inserting user: ', e));
insertOne()returnsnullon ID duplication.
Insert Multiple Documents
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.insertMany([
{ id: '2', name: 'Bob', email: '[email protected]', age: 25 },
{ id: '3', name: 'Charlie', email: '[email protected]', age: 35 },
{ id: '4', name: 'Homer', email: '[email protected]', age: 45 },
{ id: '5', name: 'Marge', email: '[email protected]', age: 43 },
{ id: '6', name: 'Lisa', email: '[email protected]', age: 15 },
{ id: '7', name: 'Bart', email: '[email protected]', age: 13 },
{ id: '8', name: 'Maggie', email: '[email protected]', age: 1 }
], { returnDocuments: true })
.then((result) => {
if (result.skippedCount > 0) return console.error(`${result.skippedCount} users with these ids already exists`);
console.log('Inserted users: ', result.insertedDocuments);
})
.catch(e => console.error('Error while inserting users: ', e));| Option | Type | Default | Description |
|---|---|---|---|
| returnDocuments | boolean | false | Return inserted documents into insertedDocuments |
Always check
skippedCountto know how many documents were skipped due to ID conflicts.
Find Documents
Query documents with filters, sorting, and pagination.
Find by ID
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.findById(1)
.then((user) => {
if (!user) return console.error('User not found');
console.log('User found: ', user);
})
.catch(err => console.error('Error while finding user: ', err));
findById()returnsnullif document not found.
Find One Document
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.findOne({ email: '[email protected]' })
.then((user) => {
if (!user) return console.error('User not found');
console.log('User found: ', user);
})
.catch(e => console.error('Error while finding user: ', e));
findOne()returnsnullif document not found.
Find All Documents
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.find({})
.then(({ documents: users, pagination }) => {
console.log('All users: ', users);
})
.catch(e => console.error('Error while finding users: ', e));
find()always returns{ documents: [], pagination: {} }.
Find with Filters
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.find(
{ age: { $gte: 24 } },
{
sort: { age: -1 },
limit: 2,
fields: { id: 1, username: 1 }
})
.then(({ documents: users, pagination }) => {
console.log('All users: ', users);
})
.catch(e => console.error('Error while finding users: ', e));| Option | Type | Methods | Description |
|---|---|---|---|
| fields | object | findById, findOne, find | Field projection (1 include, -1 exclude) |
| sort | object | findOne, find | Sort results (1 ascending, -1 descending) |
| limit | number | find | Limit returned documents |
| skip | number | find | Number of documents to skip |
| page | number | find | Page number (alternative to skip) |
Update Documents
Modify existing documents using update operators.
Update by ID
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.updateById(1, { $inc: { age: 1 } })
.then((updatedUser) => {
if (!updatedUser) return console.error('User not found');
console.log('Updated user: ', updatedUser);
})
.catch(e => console.error('Error while updating user: ', e));
updateById()returnsnullif document not found.
Update One Document
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.updateOne({ email: '[email protected]' }, { lastLogin: Date.now() })
.then((updatedUser) => {
if (!updatedUser) return console.error('User not found');
console.log('Updated user: ', updatedUser);
})
.catch(e => console.error('Error while updating user: ', e));
updateOne()returnsnullif no document matched the filters.
Update Multiple Documents
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.updateMany({ age: { $gte: 18 } }, { major: true }, { returnDocuments: true })
.then((result) => {
if (result.updatedCount === 0) return console.error('No users found to update');
console.log('Updated users: ', result.updatedDocuments);
})
.catch(e => console.error('Error while updating users: ', e));| Option | Type | Default | Description |
|---|---|---|---|
| returnDocuments | boolean | false | Return updated documents into updatedDocuments |
Check
updatedCountto know how many documents were updated.
Update Operators
const users = db.collection('users');
// $set - Set a value
await users.updateOne({ email: '[email protected]' }, { $set: { verified: true } });
// or simply with
await users.updateOne({ email: '[email protected]' }, { verified: true });
// $inc - Increment
await users.updateOne({ email: '[email protected]' }, { $inc: { visits: 1 } });
// $push - Add to array
await users.updateOne({ email: '[email protected]' }, { $push: { tags: 'premium' } });
// $addToSet - Add without duplicates
await users.updateOne({ email: '[email protected]' }, { $addToSet: { roles: 'admin' } });
// $pull - Remove from array
await users.updateOne({ email: '[email protected]' }, { $pull: { tags: 'banned' } });
// $unset - Delete field
await users.updateOne({ email: '[email protected]' }, { $unset: { tempField: 1 } });Update Nested Fields
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.updateOne({ age: { $gte: 18 } }, { 'settings.language': 'en' })
.then((user) => {
if (!user) return console.error('User not found');
console.log('Updated user: ', user);
})
.catch(err => console.error('Error while updating user: ', err));Count Documents
Get the number of documents matching a filter.
Count All Documents
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.count({})
.then((usersCount) => {
console.log('Users count: ', usersCount);
})
.catch(e => console.error('Error while counting users: ', e));Count with Filters
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.count({ age: { $gte: 28 } })
.then((usersCount) => {
console.log('Users count: ', usersCount);
})
.catch(e => console.error('Error while counting users: ', e));Delete Documents
Remove documents from collections.
Delete by ID
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.deleteById(2)
.then((user) => {
if (!user) return console.error('User not found');
console.log('User with ID 2 successfully deleted.');
})
.catch(e => console.error('Error while deleting user: ', e));
deleteById()returnsfalseif document not found by ID.
Delete One Document
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.deleteOne({ email: '[email protected]' })
.then((deletedId) => {
if (!deletedId) return console.error('User not found');
console.log('User with email "[email protected]" successfully deleted. Deleted ID :', deletedId);
})
.catch(e => console.error('Error while deleting user: ', e));
deleteOne()returnsnullif no document matched the filters.
Delete Multiple by IDs
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.deleteMany({ id: { $in: [1, 5, 8] } })
.then((result) => {
if (result.deletedCount === 0) return console.error('No users found to delete');
if (result.deletedCount === 3) console.log(`All ${result.deletedCount} users deleted successfully`);
else console.error(`Only ${result.deletedCount} users were deleted`);
})
.catch(e => console.error('Error while deleting users: ', e));Check
deletedCountto know how many documents were deleted.
Delete with Filters
const db = new (require('davidb'))({ debug: true });
db.collection('users')
.deleteMany({ age: { $gte: 26 } })
.then((result) => {
if (result.deletedCount > 0) console.log(`Deleted ${result.deletedCount} users`);
else console.error('No users found to delete');
})
.catch(e => console.error('Error while deleting users: ', e));Filters & Operators
daviDB supports MongoDB-like query operators for filtering documents.
Complete Operators Reference
| Operator | Category | Description | Example |
|---|---|---|---|
| $eq | Comparison | Matches values that are equal to a specified value | { age: { $eq: 25 } } |
| $ne | Comparison | Matches values that are not equal to a specified value | { status: { $ne: 'banned' } } |
| $gt | Comparison | Matches values that are greater than a specified value | { age: { $gt: 18 } } |
| $gte | Comparison | Matches values that are greater than or equal to a specified value | { age: { $gte: 18 } } |
| $lt | Comparison | Matches values that are less than a specified value | { age: { $lt: 65 } } |
| $lte | Comparison | Matches values that are less than or equal to a specified value | { age: { $lte: 65 } } |
| $in | Comparison | Matches any of the values specified in an array | { status: { $in: ['active', 'pending'] } } |
| $nin | Comparison | Matches none of the values specified in an array | { role: { $nin: ['admin', 'mod'] } } |
| $and | Logical | Joins query clauses with a logical AND | { $and: [{ age: { $gte: 18 } }, { status: 'active' }] } |
| $or | Logical | Joins query clauses with a logical OR | { $or: [{ role: 'admin' }, { role: 'mod' }] } |
| $not | Logical | Inverts the effect of a query expression | { age: { $not: { $lt: 18 } } } |
| $nor | Logical | Joins query clauses with a logical NOR | { $nor: [{ banned: true }, { deleted: true }] } |
| $exists | Element | Matches documents that have the specified field | { email: { $exists: true } } |
| $regex | Evaluation | Matches values that match a specified regular expression | { email: { $regex: /@gmail\.com$/ } } |
| $mod | Evaluation | Performs a modulo operation on the value of a field | { age: { $mod: [2, 0] } } |
Comparison Operators
const users = db.collection('users');
// $eq - Equal
await users.find({ age: 25 });
await users.find({ age: { $eq: 25 } });
// $ne - Not equal
await users.find({ status: { $ne: 'banned' } });
// $gt, $gte, $lt, $lte - Greater/Less than
await users.find({ age: { $gte: 18, $lt: 65 } });
// $in - In array
await users.find({ status: { $in: ['active', 'pending'] } });
// $nin - Not in array
await users.find({ role: { $nin: ['admin', 'moderator'] } });Logical Operators
By default, multiple filter conditions separated by commas act as a logical AND. You only need $and for complex nested queries.
// Implicit $and - Multiple conditions (recommended)
await users.find({ age: { $gte: 18 }, status: 'active' });
// Explicit $and - Same result, more verbose
await users.find({
$and: [
{ age: { $gte: 18 } },
{ status: 'active' }
]
});
// $or - At least one condition must match
await users.find({
$or: [
{ role: 'admin' },
{ role: 'moderator' }
]
});
// $not - Negation
await users.find({ age: { $not: { $lt: 18 } } });
// $nor - None of the conditions match
await users.find({
$nor: [
{ banned: true },
{ deleted: true }
]
});
// Combining implicit AND with $or
await users.find({
status: 'active', // Implicit AND
$or: [
{ role: 'admin' },
{ role: 'moderator' }
]
});Special Operators
// $exists - Field exists
await users.find({ email: { $exists: true } });
// $regex - Regular expression
await users.find({ email: { $regex: /@gmail\.com$/ } });
// $mod - Modulo operation (even ages)
await users.find({ age: { $mod: [2, 0] } });Array Queries
// Match value in array
await users.find({ tags: 'premium' });
// Match any value
await users.find({ tags: { $in: ['vip', 'premium'] } });Nested Fields (Dot Notation)
await users.find({ 'settings.theme': 'dark' });
await users.find({ 'address.city': 'Paris' });Pagination
daviDB provides flexible pagination with automatic metadata.
Basic Pagination
const db = new (require('davidb'))({ debug: true });
const productList = [];
for (let i = 1; i <= 50; i++) {
productList.push({
name: `Product ${i}`,
price: Math.floor(Math.random() * 1000) + 10,
category: ['Electronics', 'Clothing', 'Books', 'Home'][Math.floor(Math.random() * 4)],
stock: Math.floor(Math.random() * 100)
});
}
db.collection('products').insertMany(productList)
.catch(e => { console.error('Error while inserting products: ', e); });
db.collection('products').find({}, { limit: 3, page: 4 })
.then(({ documents: products, pagination }) => {
console.log('All products : ', products);
})
.catch(e => { console.error('Error while finding products: ', e); });
db.collection('products')
.count({ category: { $in: ['Electronics', 'Books'] } })
.then((productsCount) => {
console.log('Products count in Electronics or Books category: ', productsCount);
})
.catch(e => console.error('Error while counting products: ', e));Using Skip
const db = new (require('davidb'))({ debug: true });
db.collection('products').find({}, { limit: 2, skip: 40 })
.then(({ documents: products, pagination }) => {
console.log('All products : ', products);
})
.catch(e => { console.error('Error while finding products: ', e); });Field Projection
Select or exclude specific fields from query results.
Include Fields
const db = new (require('davidb'))({ debug: true });
db.collection('products').find({}, { fields: { id: 1, name: 1, price: 1 } })
.then(({ documents: products, pagination }) => {
console.log('All products : ', products);
})
.catch(e => { console.error('Error while finding products: ', e); });Exclude Fields
const db = new (require('davidb'))({ debug: true });
db.collection('products').find({}, { fields: { name: -1, price: -1 } })
.then(({ documents: products, pagination }) => {
console.log('All products : ', products);
})
.catch(e => { console.error('Error while finding products: ', e); });Note: Cannot mix inclusion and exclusion in the same projection.
Database Management
Manage collections and database status.
List Collections
const db = new (require('davidb'))({ debug: true });
db.listCollections()
.then(({ collections }) => { console.log('All collections: ', collections) });Get Collection Status
const db = new (require('davidb'))({ debug: true });
db.collectionStatus('users')
.then(console.log);
// or
db.collection('users')
.status()
.then(console.log);Drop Collection
const daviDB = require('davidb');
const db = new daviDB({
debug: true
});
db.collection('users').drop()
.then(deleted => {
console.log(deleted);
})
.catch(e => { console.log('Error while dropping collection: ', e.message); });Close Database
db.close()
.then(() => { console.log('Database closed'); })
.catch(e => { console.log('Error while closing database: ', e.message); });
// Flush all pending writes and close connections
// By default, daviDB auto close when script finish.
// Use this method only if you want to close it before the end of the script.Events
daviDB can emit real-time events whenever documents are inserted, updated, deleted, or a collection is dropped. Subscribe with db.subscribe() to react to changes as they happen.
In Local Mode, events are emitted in-process. In HTTP Mode, events are streamed from the server over Server-Sent Events (SSE) — note that HTTPAdapter is currently unavailable (see Introduction).
By default, events are enabled. Use
enableEvents: falseoption to disable them.
const db = new (require('davidb'))({ debug: true, enableEvents: false });Subscribe to All Events
const db = new (require('davidb'))({ debug: true });
db.subscribe((event) => {
console.log('Event received: ', event);
});Subscribe to Specific Collections
const db = new (require('davidb'))({ debug: true });
db.subscribe('users', (event) => {
console.log('Users collection event: ', event);
});
// Subscribe to all operations from multiple collections
db.subscribe(['users', 'orders'], (event) => {
console.log('Event: ', event);
});Subscribe to Specific Operations
const db = new (require('davidb'))({ debug: true });
db.subscribe('users', 'insert', (event) => {
console.log('New user inserted: ', event);
});
// Subscribe to multiple operations for a collection
db.subscribe('users', ['insert', 'delete'], (event) => {
console.log('User inserted or deleted: ', event);
});
// Subscribe to all operations for multiple collections
db.subscribe(['users', 'orders'], '*', (event) => {
console.log('Event: ', event);
});Allowed operations:
insert,update,delete,drop,*(all).
Unsubscribe
Useless to unsubscribe when closing the nodejs application, that will automatically unsubscribe all the listeners.
const db = new (require('davidb'))({ debug: true });
const unsubscribe = db.subscribe('users', (event) => {
console.log('Event: ', event);
});
// Later, stop listening
unsubscribe();
// Or, unsubscribe by reference
const listener = (event) => console.log('Event: ', event);
db.subscribe('users', listener);
db.unsubscribe(listener);Event Payload
{
collection: 'users',
operation: 'insert', // 'insert' | 'update' | 'delete' | 'drop'
emittedAt: 1719000000000,
method: 'insertOne', // originating method, e.g. 'insertOne', 'updateMany', 'deleteById'
duration: 3, // operation duration in ms
count: 1, // number of documents affected
ids: [1], // affected document ids
documents: [ /* ... */ ], // affected documents (omitted if count exceeds maxEventDocuments)
truncated: false, // true if documents were omitted due to maxEventDocuments
filters: { /* ... */ }, // present for update/delete operations
update: { /* ... */ } // present for update operations ($set, $inc, etc.)
}Use
maxEventDocuments(default100) to control how many documents are included in thedocumentsfield before it gets truncated. See Configuration.
Error Handling
All operations throw a standardized response format.
Error Object Structure
{
status: 400,
code: 'DB:FILTERS:BAD_REQUEST',
message: 'Invalid filters query >> $existing <<',
received: '$existing',
allowedOps: [
'$eq', '$ne',
'$gt', '$gte',
'$lt', '$lte',
'$in', '$nin',
'$exists', '$regex',
'$and', '$or',
'$nor', '$not',
'$mod'
]
}
{
status: 400,
code: 'DB:OPTIONS:BAD_REQUEST',
message: 'Invalid query option >> field <<',
received: 'field',
allowedOps: [ 'sort', 'skip', 'limit', 'fields', 'page', 'returnDocuments' ]
}
{
status: 400,
code: 'DB:UPDATE:BAD_REQUEST',
message: 'Invalid update operator >> $include <<',
received: '$include',
allowedOps: [ '$set', '$unset', '$inc', '$push', '$pull', '$addToSet' ]
}
{
status: 400,
code: 'DB:UPDATE_BY_ID:BAD_REQUEST',
message: 'updateById operation requires a valid document ID.'
}Acknowledgments
A heartfelt thank you to emi.py for his dedicated testing and debugging efforts. His work was instrumental in identifying and fixing critical issues related to:
- Parallel operations across multiple collections
- Cache eviction during active operations
- Collection name handling in concurrent requests
- Overall stability and performance improvements
daviDB is more reliable thanks to his contributions.
daviDB — Lightweight. Reliable. Tested with passion. 💙
