LiekoDB Documentation
A lightweight, MongoDB-like JSON database for Node.js with local and HTTP modes
Introduction
LiekoDB 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 LiekoDB server using HTTP client
Installation
NPM
npm install liekodb
Yarn
yarn add liekodb
Basic Usage
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const users = db.collection('users');
Configuration
Local Mode (Node.js)
const db = new LiekoDB({
storagePath: './storage', // Storage directory
autoSaveInterval: 5000, // Auto-save every 5s
debug: true // Enable detailed logs
});
HTTP Mode (Client)
const db = new LiekoDB({
databaseUrl: '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
});
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
storagePath |
string | './storage' | Directory for JSON files (Local mode) |
autoSaveInterval |
number | 5000 | Auto-save interval in milliseconds |
debug |
boolean | false | Enable detailed logging |
databaseUrl |
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.
Insert Single Document
const { error, data } = await users.insert({
username: 'alice',
email: 'alice@example.com',
age: 28
});
console.log(data);
// {
// insertedCount: 1,
// insertedIds: ['abc123def456'],
// totalDocuments: 1
// }
Insert Multiple Documents
const { error, data } = await users.insert([
{ username: 'bob', email: 'bob@example.com', age: 32 },
{ username: 'charlie', email: 'charlie@example.com', age: 25 }
]);
console.log(data);
// {
// insertedCount: 2,
// firstId: '1a2b3c4d5e6f7890',
// lastId: '1a2b3c4d5e6f7891',
// totalDocuments: 3
// }
Upsert (Insert or Update)
// If document with this ID exists, it will be updated
// Otherwise, it will be inserted
const { error, data } = await users.insert({
id: 'user_123',
username: 'alice',
email: 'alice_updated@example.com'
});
console.log(data);
// {
// insertedCount: 0,
// updatedCount: 1,
// totalDocuments: 3
// }
id, createdAt, and updatedAt
fields.
Find Documents
Query documents with filters, sorting, and pagination.
Find All Documents
const { error, data } = await users.find();
console.log(data.foundDocuments);
// [ { id: '...', username: 'alice', ... }, ... ]
Find with Filters
const { error, data } = await users.find(
{ age: { $gte: 25 } },
{
sort: { age: -1 },
limit: 10,
fields: { username: 1, email: 1 }
}
);
console.log(data);
// {
// foundCount: 5,
// foundDocuments: [...],
// totalDocuments: 20
// }
Find One Document
const { error, data } = await users.findOne(
{ email: 'alice@example.com' }
);
console.log(data); // Document or null
Find by ID
const { error, data } = await users.findById('user_123');
console.log(data); // Document or null
Query Options
| Option | Type | Description |
|---|---|---|
sort |
object | Sort results (1 ascending, -1 descending) |
limit |
number | Maximum number of results |
skip |
number | Number of documents to skip |
page |
number | Page number (alternative to skip) |
fields |
object | Field projection (1 include, -1 exclude) |
Update Documents
Modify existing documents using update operators.
Update by ID
const { error, data } = await users.updateById(
'user_123',
{ $inc: { loginCount: 1 } },
{ returnType: 'document' }
);
console.log(data.document); // Updated document
Update Multiple Documents
const { error, data } = await users.update(
{ age: { $lt: 30 } },
{ $set: { status: 'young' } },
{ returnType: 'count' }
);
console.log(data);
// {
// updatedCount: 15,
// totalDocuments: 100
// }
Update Operators
// $set - Set a value
await users.update({}, { $set: { verified: true } });
// $inc - Increment
await users.update({}, { $inc: { visits: 1 } });
// $push - Add to array
await users.update({}, { $push: { tags: 'premium' } });
// $addToSet - Add without duplicates
await users.update({}, { $addToSet: { roles: 'admin' } });
// $pull - Remove from array
await users.update({}, { $pull: { tags: 'banned' } });
// $unset - Remove field
await users.update({}, { $unset: { tempField: 1 } });
Update Nested Fields
await domains.update(
{ name: 'example.com' },
{ $set: { 'serverMOTD.raw': 'New message' } }
);
Return Types
| Type | Description |
|---|---|
count |
Returns only the count of updated documents |
ids |
Returns IDs of updated documents |
documents |
Returns full updated documents |
Delete Documents
Remove documents from collections.
Delete by ID
const { error, data } = await users.deleteById('user_123');
console.log(data);
// {
// deletedCount: 1,
// deletedId: 'user_123'
// }
Delete Multiple by IDs
const { error, data } = await users.delete({
id: { $in: ['user_123', 'user_456', 'user_789'] }
});
console.log(data);
// {
// collectionName: 'users',
// deletedCount: 3
// }
Delete with Filters
const { error, data } = await users.delete({
age: { $lt: 18 }
});
console.log(data);
// {
// collectionName: 'users',
// deletedCount: 5
// }
Drop Collection
const { error, data } = await users.drop();
console.log(data);
// {
// collectionName: 'users',
// dropped: true
// }
delete() method requires
filters to prevent accidental deletion of all documents. Use drop() to delete the entire collection.
Count Documents
Get the number of documents matching a filter.
Count All Documents
const { error, data } = await users.count();
console.log(data); // 150
Count with Filters
const { error, data } = await users.count({
age: { $gte: 25 }
});
console.log(data); // 42
Filters & Operators
LiekoDB 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
// $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
await users.find({
age: { $mod: [2, 0] } // Even ages
});
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 domains.find({
'serverMOTD.raw': 'A Minecraft Server'
});
await users.find({
'address.city': 'Paris'
});
Pagination
LiekoDB provides flexible pagination with automatic metadata.
Basic Pagination
const { error, data } = await users.find({}, {
limit: 20,
page: 2
});
console.log(data.pagination);
// {
// page: 2,
// limit: 20,
// total: 150,
// totalPages: 8,
// hasNext: true,
// hasPrev: true,
// nextPage: 3,
// prevPage: 1,
// startIndex: 21,
// endIndex: 40
// }
Using Skip
const { error, data } = await users.find({}, {
limit: 20,
skip: 40 // Skip first 40 documents
});
Sorting with Pagination
const { error, data } = await users.find(
{ status: 'active' },
{
sort: { createdAt: -1 },
limit: 20,
page: 1
}
);
Field Projection
Select or exclude specific fields from query results.
Include Fields
const { error, data } = await users.find({}, {
fields: { username: 1, email: 1 }
});
// Only returns: { username: '...', email: '...' }
Exclude Fields
const { error, data } = await users.find({}, {
fields: { password: -1, apiKey: -1 }
});
// Returns all fields except password and apiKey
Database Management
Manage collections and database status.
List Collections
const collections = await db.listCollections();
console.log(collections);
// [
// {
// name: 'users',
// totalDocuments: 150,
// sizeBytes: 45120,
// sizeFormatted: '44.06 KB'
// },
// {
// name: 'domains',
// totalDocuments: 42,
// sizeBytes: 12340,
// sizeFormatted: '12.05 KB'
// }
// ]
Database Status
const status = await db.status();
console.log(status);
// {
// storagePath: './storage',
// collections: [...],
// totalCollections: 5,
// totalDocuments: 1250,
// totalCollectionsSize: 2048576,
// totalCollectionsSizeFormatted: '2.00 MB'
// }
Close Database
// Flush all pending writes and close connections
await db.close();
Error Handling
All operations return a standardized response format with error handling.
Response Format
const { error, data } = await users.findById('unknown_id');
if (error) {
console.error('Error:', error.message);
console.error('Code:', error.code);
return;
}
console.log('Result:', data);
Error Object Structure
{
error: {
message: "Error description",
code: 500
}
}
Common Error Scenarios
| Error | Description |
|---|---|
Collection name must be a non-empty string |
Invalid collection name provided |
Filters must be a non-null plain object |
Invalid filter format |
Invalid query operator |
Unknown or unsupported operator used |
Document not found |
No document matches the query |
Delete operation requires filters |
Must provide filters to prevent accidental deletion |
Data Objects
Document Structure
All documents automatically include these fields:
-
id
string
Unique identifier (auto-generated if not provided)
-
createdAt
string
ISO 8601 timestamp of creation
-
updatedAt
string
ISO 8601 timestamp of last update
Example Document
{
"id": "abc123def456",
"username": "alice",
"email": "alice@example.com",
"age": 28,
"createdAt": "2026-01-05T10:30:00.000Z",
"updatedAt": "2026-01-05T10:30:00.000Z"
}
Best Practices
Follow these guidelines to use LiekoDB effectively and avoid common pitfalls.
Collection Design
// ✅ Good: Descriptive, singular names
const users = db.collection('users');
const orders = db.collection('orders');
// ❌ Avoid: Generic or unclear names
const data = db.collection('data');
const temp = db.collection('temp');
-
Use descriptive names
Choose clear, meaningful collection names that describe the data they contain
-
Keep names consistent
Use a consistent naming convention (camelCase or snake_case)
-
Avoid special characters
Stick to alphanumeric characters, hyphens, and underscores
Query Optimization
// ✅ Good: Use field projection to reduce data transfer
const { data } = await users.find(
{ status: 'active' },
{ fields: { username: 1, email: 1 } }
);
// ✅ Good: Use pagination for large datasets
const { data } = await users.find(
{},
{ limit: 50, page: 1 }
);
// ❌ Avoid: Fetching all data without limits
const { data } = await users.find({});
-
Always use pagination
Limit results to avoid loading large datasets into memory
-
Project only needed fields
Use field projection to reduce response size
-
Use specific filters
Narrow queries with precise filters to improve performance
Error Handling
// ✅ Good: Always check for errors
const { error, data } = await users.findById('user_123');
if (error) {
console.error('Query failed:', error.message);
return;
}
console.log('User found:', data);
// ✅ Good: Use try-catch for operations
try {
const result = await users.insert({ username: 'alice' });
if (result.error) {
console.error('Insert failed:', result.error);
}
} catch (err) {
console.error('Unexpected error:', err);
}
-
Always handle errors
Check the error field in responses before accessing data
-
Use try-catch blocks
Wrap operations in try-catch to handle unexpected errors
-
Log errors appropriately
Log errors with context to facilitate debugging
Data Validation
// ✅ Good: Validate data before insertion
const validateUser = (user) => {
if (!user.username || user.username.length < 3) {
throw new Error('Username must be at least 3 characters');
}
if (!user.email || !user.email.includes('@')) {
throw new Error('Invalid email address');
}
return true;
};
try {
validateUser(userData);
await users.insert(userData);
} catch (err) {
console.error('Validation failed:', err.message);
}
-
Validate before operations
Check data integrity before inserting or updating
-
Use consistent schemas
Maintain consistent document structure within collections
-
Handle type conversions
Ensure data types are correct before storing
Update Operations
// ✅ Good: Use update operators for atomic updates
await users.updateById('user_123', {
$inc: { loginCount: 1 },
$set: { lastLogin: new Date().toISOString() }
});
// ✅ Good: Update specific fields
await users.update(
{ status: 'pending' },
{ $set: { status: 'active' } }
);
// ❌ Avoid: Replacing entire documents unnecessarily
await users.updateById('user_123', {
username: 'alice',
email: 'alice@example.com',
// ... all other fields
});
-
Use atomic operators
Prefer $inc, $push, $set for safer, more efficient updates
-
Update only changed fields
Avoid replacing entire documents when only few fields change
-
Check update results
Verify updatedCount to ensure operations succeeded
Delete Operations
// ✅ Good: Always provide filters for delete()
await users.delete({ inactive: true, lastLogin: { $lt: '2025-01-01' } });
// ✅ Good: Use deleteById for single documents
await users.deleteById('user_123');
// ✅ Good: Use drop() to clear entire collection
await users.drop();
// ❌ Avoid: This will throw an error
await users.delete({}); // Error: filters required
Performance Tips
-
Use bulk inserts
Insert multiple documents at once instead of individual operations
-
Enable debug mode during development
Set
debug: trueto monitor performance and identify bottlenecks -
Adjust auto-save interval
Balance between data safety and performance (default: 5000ms)
-
Close database on shutdown
Always call
db.close()to ensure all data is saved -
Monitor collection sizes
Use
db.status()to track storage usage
HTTP Mode Best Practices
// ✅ Good: Configure connection pooling
const db = new LiekoDB({
databaseUrl: 'http://127.0.0.1:8050',
token: 'your-auth-token',
poolSize: 10, // Adjust based on concurrent requests
timeout: 15000 // Set appropriate timeout
});
// ✅ Good: Reuse database instance
const dbInstance = new LiekoDB({ token: 'your-token' });
const users = dbInstance.collection('users');
const orders = dbInstance.collection('orders');
-
Secure your token
Never expose authentication tokens in client-side code
-
Configure pool size
Adjust poolSize based on expected concurrent requests
-
Set appropriate timeouts
Balance between reliability and responsiveness
-
Reuse database instances
Create one instance and reuse for all collections
Basic CRUD Operations
Simple example covering insert, find, update, and delete operations.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const users = db.collection('users');
async function basicCRUD() {
// Insert users
console.log('=== Inserting users ===');
await users.insert([
{ username: 'alice', email: 'alice@example.com', age: 28, role: 'admin' },
{ username: 'bob', email: 'bob@example.com', age: 32, role: 'user' },
{ username: 'charlie', email: 'charlie@example.com', age: 25, role: 'user' }
]);
// Find all users
console.log('\n=== Finding all users ===');
const { data: allUsers } = await users.find();
console.log(`Found ${allUsers.foundCount} users`);
// Find specific user
console.log('\n=== Finding user by email ===');
const { data: alice } = await users.findOne({ email: 'alice@example.com' });
console.log('User:', alice);
// Update user
console.log('\n=== Updating user ===');
await users.update(
{ username: 'alice' },
{ $set: { age: 29 } }
);
// Delete user
console.log('\n=== Deleting user ===');
await users.delete({ username: 'charlie' });
// Final count
const count = await users.count();
console.log(`\nFinal count: ${count.data} users`);
await db.close();
}
basicCRUD().catch(console.error);
Pagination Example
Implement pagination with sorting and field projection.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const products = db.collection('products');
async function paginationExample() {
// Insert sample products
console.log('=== Inserting products ===');
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)
});
}
await products.insert(productList);
// Paginated query
console.log('\n=== Page 1 (10 items per page) ===');
const { data: page1 } = await products.find(
{ price: { $gte: 100 } },
{
sort: { price: -1 },
limit: 10,
page: 1,
fields: { name: 1, price: 1, category: 1 }
}
);
console.log(`Found ${page1.foundCount} products on this page`);
console.log(`Total matching: ${page1.pagination.total}`);
console.log(`Page ${page1.pagination.page} of ${page1.pagination.totalPages}`);
console.log(`Has next page: ${page1.pagination.hasNext}`);
page1.foundDocuments.forEach((product, idx) => {
console.log(`${idx + 1}. ${product.name} - $${product.price}`);
});
// Get next page
if (page1.pagination.hasNext) {
console.log('\n=== Page 2 ===');
const { data: page2 } = await products.find(
{ price: { $gte: 100 } },
{
sort: { price: -1 },
limit: 10,
page: 2,
fields: { name: 1, price: 1 }
}
);
page2.foundDocuments.forEach((product, idx) => {
console.log(`${idx + 1}. ${product.name} - $${product.price}`);
});
}
await db.close();
}
paginationExample().catch(console.error);
Advanced Filtering
Complex queries with multiple operators and nested conditions.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const orders = db.collection('orders');
async function advancedFiltering() {
// Insert sample orders
console.log('=== Inserting orders ===');
await orders.insert([
{
orderId: 'ORD001',
customer: { name: 'Alice', email: 'alice@example.com', vip: true },
items: ['laptop', 'mouse', 'keyboard'],
total: 1250.50,
status: 'completed',
shippedAt: '2025-01-05T10:00:00Z'
},
{
orderId: 'ORD002',
customer: { name: 'Bob', email: 'bob@example.com', vip: false },
items: ['phone'],
total: 899.99,
status: 'pending',
shippedAt: null
},
{
orderId: 'ORD003',
customer: { name: 'Charlie', email: 'charlie@gmail.com', vip: true },
items: ['tablet', 'case'],
total: 450.00,
status: 'completed',
shippedAt: '2025-01-07T14:30:00Z'
},
{
orderId: 'ORD004',
customer: { name: 'Diana', email: 'diana@example.com', vip: false },
items: ['headphones'],
total: 150.00,
status: 'cancelled',
shippedAt: null
},
{
orderId: 'ORD005',
customer: { name: 'Eve', email: 'eve@gmail.com', vip: true },
items: ['monitor', 'cable', 'adapter'],
total: 650.00,
status: 'pending',
shippedAt: null
}
]);
// Complex filter 1: VIP customers with completed orders over $500
console.log('\n=== VIP customers with completed orders > $500 ===');
const { data: vipOrders } = await orders.find({
'customer.vip': true,
status: 'completed',
total: { $gt: 500 }
});
console.log(`Found ${vipOrders.foundCount} orders`);
vipOrders.foundDocuments.forEach(order => {
console.log(`- ${order.orderId}: ${order.customer.name} - $${order.total}`);
});
// Complex filter 2: Gmail users OR orders with multiple items
console.log('\n=== Gmail users OR orders with 3+ items ===');
const { data: complexOrders } = await orders.find({
$or: [
{ 'customer.email': { $regex: /@gmail\.com$/ } },
{ items: { $exists: true } }
]
});
const filtered = complexOrders.foundDocuments.filter(order =>
order.customer.email.includes('@gmail.com') || order.items.length >= 3
);
console.log(`Found ${filtered.length} orders`);
filtered.forEach(order => {
console.log(`- ${order.orderId}: ${order.items.length} items, ${order.customer.email}`);
});
// Complex filter 3: Pending or cancelled, not shipped, under $1000
console.log('\n=== Pending/Cancelled, not shipped, under $1000 ===');
const { data: pendingOrders } = await orders.find({
status: { $in: ['pending', 'cancelled'] },
shippedAt: null,
total: { $lt: 1000 }
});
console.log(`Found ${pendingOrders.foundCount} orders`);
pendingOrders.foundDocuments.forEach(order => {
console.log(`- ${order.orderId}: ${order.status} - $${order.total}`);
});
// Complex filter 4: NOT cancelled AND (VIP OR total > $800)
console.log('\n=== Active orders: VIP or high value ===');
const { data: activeOrders } = await orders.find({
status: { $ne: 'cancelled' },
$or: [
{ 'customer.vip': true },
{ total: { $gte: 800 } }
]
});
console.log(`Found ${activeOrders.foundCount} orders`);
activeOrders.foundDocuments.forEach(order => {
console.log(`- ${order.orderId}: ${order.customer.name} (VIP: ${order.customer.vip}) - $${order.total}`);
});
// Complex filter 5: NOR query - exclude both cancelled and low-value orders
console.log('\n=== Exclude cancelled AND orders under $200 ===');
const { data: qualityOrders } = await orders.find({
$nor: [
{ status: 'cancelled' },
{ total: { $lt: 200 } }
]
});
console.log(`Found ${qualityOrders.foundCount} orders`);
qualityOrders.foundDocuments.forEach(order => {
console.log(`- ${order.orderId}: ${order.status} - $${order.total}`);
});
await db.close();
}
advancedFiltering().catch(console.error);
Update Operations
Demonstrate various update operators including arrays and nested fields.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const profiles = db.collection('profiles');
async function updateOperations() {
// Insert initial profile
console.log('=== Inserting profile ===');
const { data: insertResult } = await profiles.insert({
username: 'john_doe',
email: 'john@example.com',
stats: {
loginCount: 0,
lastLogin: null,
points: 100
},
tags: ['new'],
preferences: {
theme: 'light',
notifications: true
}
});
const userId = insertResult.insertedIds[0];
// $inc - Increment login count
console.log('\n=== Incrementing login count ===');
await profiles.updateById(userId, {
$inc: { 'stats.loginCount': 1 },
$set: { 'stats.lastLogin': new Date().toISOString() }
});
// $push - Add tags
console.log('\n=== Adding tags ===');
await profiles.updateById(userId, {
$push: { tags: 'active' }
});
// $addToSet - Add tag without duplicates
console.log('\n=== Adding unique tag ===');
await profiles.updateById(userId, {
$addToSet: { tags: 'verified' }
});
await profiles.updateById(userId, {
$addToSet: { tags: 'verified' } // Won't create duplicate
});
// Multiple operations
console.log('\n=== Multiple updates ===');
await profiles.updateById(userId, {
$inc: { 'stats.points': 50, 'stats.loginCount': 1 },
$set: { 'preferences.theme': 'dark' },
$push: { tags: 'premium' }
});
// $pull - Remove tag
console.log('\n=== Removing tag ===');
await profiles.updateById(userId, {
$pull: { tags: 'new' }
});
// Get final state
const { data: finalProfile } = await profiles.findById(userId);
console.log('\n=== Final profile ===');
console.log(JSON.stringify(finalProfile, null, 2));
await db.close();
}
updateOperations().catch(console.error);
Bulk Operations & Performance
Efficiently handle large datasets with bulk inserts and batch updates.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
autoSaveInterval: 10000,
debug: true
});
const analytics = db.collection('analytics');
async function bulkOperations() {
console.log('=== Bulk Insert Performance Test ===');
// Generate large dataset
const events = [];
const startTime = Date.now();
for (let i = 1; i <= 10000; i++) {
events.push({
eventType: ['click', 'view', 'purchase', 'signup'][Math.floor(Math.random() * 4)],
userId: `user_${Math.floor(Math.random() * 1000)}`,
timestamp: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
value: Math.floor(Math.random() * 100),
metadata: {
device: ['mobile', 'desktop', 'tablet'][Math.floor(Math.random() * 3)],
country: ['US', 'UK', 'FR', 'DE', 'JP'][Math.floor(Math.random() * 5)]
}
});
}
console.log(`Generated ${events.length} events in ${Date.now() - startTime}ms`);
// Bulk insert
const insertStart = Date.now();
const { data: insertResult } = await analytics.insert(events);
console.log(`Inserted ${insertResult.insertedCount} documents in ${Date.now() - insertStart}ms`);
// Bulk update - mark recent purchases
console.log('\n=== Bulk Update ===');
const updateStart = Date.now();
const recentDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const { data: updateResult } = await analytics.update(
{
eventType: 'purchase',
timestamp: { $gte: recentDate }
},
{ $set: { processed: true } },
{ returnType: 'count' }
);
console.log(`Updated ${updateResult.updatedCount} documents in ${Date.now() - updateStart}ms`);
// Aggregate queries
console.log('\n=== Statistics ===');
const { data: byType } = await analytics.find();
const typeCount = {};
byType.foundDocuments.forEach(event => {
typeCount[event.eventType] = (typeCount[event.eventType] || 0) + 1;
});
console.log('Events by type:', typeCount);
const { data: mobileUsers } = await analytics.find({
'metadata.device': 'mobile',
eventType: 'purchase'
});
console.log(`Mobile purchases: ${mobileUsers.foundCount}`);
// Cleanup old events
console.log('\n=== Cleanup ===');
const oldDate = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString();
const { data: deleteResult } = await analytics.delete({
timestamp: { $lt: oldDate }
});
console.log(`Deleted ${deleteResult.deletedCount} old events`);
const finalCount = await analytics.count();
console.log(`Final count: ${finalCount.data} events`);
await db.close();
}
bulkOperations().catch(console.error);
E-commerce Store Example
Complete example simulating an e-commerce system with products, orders, and inventory management.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const products = db.collection('products');
const customers = db.collection('customers');
const orders = db.collection('orders');
async function ecommerceExample() {
// Setup products
console.log('=== Setting up products ===');
await products.insert([
{ sku: 'LAPTOP-001', name: 'Pro Laptop', price: 1299.99, stock: 15, category: 'Electronics' },
{ sku: 'MOUSE-001', name: 'Wireless Mouse', price: 29.99, stock: 50, category: 'Accessories' },
{ sku: 'KEYBOARD-001', name: 'Mechanical Keyboard', price: 89.99, stock: 30, category: 'Accessories' },
{ sku: 'MONITOR-001', name: '4K Monitor', price: 449.99, stock: 20, category: 'Electronics' },
{ sku: 'HEADSET-001', name: 'Gaming Headset', price: 79.99, stock: 25, category: 'Accessories' }
]);
// Register customers
console.log('\n=== Registering customers ===');
const { data: customer1 } = await customers.insert({
name: 'Alice Johnson',
email: 'alice@example.com',
address: {
street: '123 Main St',
city: 'New York',
country: 'US'
},
loyaltyPoints: 0,
orders: []
});
const customerId = customer1.insertedIds[0];
// Create order
console.log('\n=== Creating order ===');
const orderItems = [
{ sku: 'LAPTOP-001', quantity: 1, price: 1299.99 },
{ sku: 'MOUSE-001', quantity: 2, price: 29.99 }
];
const orderTotal = orderItems.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const { data: orderResult } = await orders.insert({
customerId: customerId,
items: orderItems,
total: orderTotal,
status: 'pending',
shippingAddress: {
street: '123 Main St',
city: 'New York',
country: 'US'
}
});
const orderId = orderResult.insertedIds[0];
// Update inventory
console.log('\n=== Updating inventory ===');
for (const item of orderItems) {
await products.update(
{ sku: item.sku },
{ $inc: { stock: -item.quantity } }
);
}
// Add loyalty points (10% of total)
const pointsEarned = Math.floor(orderTotal * 0.1);
await customers.updateById(customerId, {
$inc: { loyaltyPoints: pointsEarned },
$push: { orders: orderId }
});
// Process order
console.log('\n=== Processing order ===');
await orders.updateById(orderId, {
$set: {
status: 'shipped',
shippedAt: new Date().toISOString()
}
});
// Generate reports
console.log('\n=== Sales Report ===');
// Low stock products
const { data: lowStock } = await products.find(
{ stock: { $lt: 20 } },
{ sort: { stock: 1 } }
);
console.log(`\nLow stock products (${lowStock.foundCount}):`);
lowStock.foundDocuments.forEach(product => {
console.log(`- ${product.name}: ${product.stock} units`);
});
// Customer summary
const { data: customer } = await customers.findById(customerId);
console.log(`\nCustomer: ${customer.name}`);
console.log(`Loyalty Points: ${customer.loyaltyPoints}`);
console.log(`Total Orders: ${customer.orders.length}`);
// Orders by status
const { data: pendingOrders } = await orders.count({ status: 'pending' });
const { data: shippedOrders } = await orders.count({ status: 'shipped' });
console.log(`\nOrders Summary:`);
console.log(`- Pending: ${pendingOrders}`);
console.log(`- Shipped: ${shippedOrders}`);
await db.close();
}
ecommerceExample().catch(console.error);
Real-time Analytics Dashboard
Track user activity and generate real-time statistics.
const LiekoDB = require('liekodb');
const db = new LiekoDB({
storagePath: './storage',
debug: true
});
const sessions = db.collection('sessions');
const pageViews = db.collection('pageViews');
async function analyticsExample() {
// Simulate user sessions
console.log('=== Generating session data ===');
const users = ['user_1', 'user_2', 'user_3', 'user_4', 'user_5'];
const pages = ['/home', '/products', '/about', '/contact', '/checkout'];
// Create sessions
for (let i = 0; i < 20; i++) {
const userId = users[Math.floor(Math.random() * users.length)];
const startTime = new Date(Date.now() - Math.random() * 24 * 60 * 60 * 1000);
const duration = Math.floor(Math.random() * 1800) + 60; // 1-30 min
await sessions.insert({
userId,
startTime: startTime.toISOString(),
endTime: new Date(startTime.getTime() + duration * 1000).toISOString(),
duration,
device: ['mobile', 'desktop'][Math.floor(Math.random() * 2)],
browser: ['Chrome', 'Firefox', 'Safari'][Math.floor(Math.random() * 3)],
pagesVisited: Math.floor(Math.random() * 10) + 1
});
}
// Generate page views
for (let i = 0; i < 100; i++) {
await pageViews.insert({
userId: users[Math.floor(Math.random() * users.length)],
page: pages[Math.floor(Math.random() * pages.length)],
timestamp: new Date(Date.now() - Math.random() * 24 * 60 * 60 * 1000).toISOString(),
duration: Math.floor(Math.random() * 300) + 10
});
}
// Analytics queries
console.log('\n=== Dashboard Analytics ===');
// Total sessions by device
const { data: allSessions } = await sessions.find();
const deviceStats = {};
allSessions.foundDocuments.forEach(session => {
deviceStats[session.device] = (deviceStats[session.device] || 0) + 1;
});
console.log('\nSessions by device:', deviceStats);
// Active users (sessions in last hour)
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const { data: recentSessions } = await sessions.find({
startTime: { $gte: oneHourAgo }
});
const activeUsers = new Set(recentSessions.foundDocuments.map(s => s.userId));
console.log(`\nActive users (last hour): ${activeUsers.size}`);
// Average session duration
const totalDuration = allSessions.foundDocuments.reduce((sum, s) => sum + s.duration, 0);
const avgDuration = Math.floor(totalDuration / allSessions.foundCount);
console.log(`Average session duration: ${Math.floor(avgDuration / 60)}m ${avgDuration % 60}s`);
// Most visited pages
const { data: allViews } = await pageViews.find();
const pageStats = {};
allViews.foundDocuments.forEach(view => {
pageStats[view.page] = (pageStats[view.page] || 0) + 1;
});
console.log('\nMost visited pages:');
Object.entries(pageStats)
.sort((a, b) => b[1] - a[1])
.forEach(([page, count]) => {
console.log(` ${page}: ${count} views`);
});
// Long sessions (> 10 minutes)
const { data: longSessions } = await sessions.find({
duration: { $gt: 600 }
});
console.log(`\nEngaged users (sessions > 10min): ${longSessions.foundCount}`);
// Mobile vs Desktop engagement
const { data: mobileSessions } = await sessions.find({ device: 'mobile' });
const mobileAvg = mobileSessions.foundDocuments.reduce((sum, s) => sum + s.pagesVisited, 0)
/ mobileSessions.foundCount;
const { data: desktopSessions } = await sessions.find({ device: 'desktop' });
const desktopAvg = desktopSessions.foundDocuments.reduce((sum, s) => sum + s.pagesVisited, 0)
/ desktopSessions.foundCount;
console.log(`\nAverage pages per session:`);
console.log(` Mobile: ${mobileAvg.toFixed(1)}`);
console.log(` Desktop: ${desktopAvg.toFixed(1)}`);
await db.close();
}
analyticsExample().catch(console.error);
npm install liekodb, then save any example to a .js file and run
with node filename.js