Lightweight, MongoDB-like JSON database for Node.js
npm install liekodbbash
npm install liekodb
`
Quick Start
$3
`javascript
// database.js
const LiekoDB = require('liekodb');
// Create a database instance
const db = new LiekoDB({
debug: true, // Enable debug logging
autoSaveInterval: 10000 // Auto-save every 10 seconds
});
// Export for use in other files
module.exports = { db };
`
$3
`javascript
// userService.js
const { db } = require('./database');
class UserService {
constructor() {
this.collection = db.collection('users');
}
async createUser(userData) {
const { error, data } = await this.collection.insert({
name: userData.name,
email: userData.email,
age: userData.age,
createdAt: new Date().toISOString()
});
if (error) throw new Error(error.message);
return data;
}
async findUserByEmail(email) {
const { error, data } = await this.collection.findOne({ email });
if (error) throw new Error(error.message);
return data;
}
}
module.exports = new UserService();
`
Core Concepts
$3
Create a database instance with optional configuration:
`javascript
const db = new LiekoDB({
debug: true, // Enable console logging
autoSaveInterval: 5000, // Auto-save interval in ms (0 to disable)
storagePath: './mydata', // Custom storage directory
token: 'your-token-here', // For HTTP adapter (browser/remote)
databaseUrl: 'http://api.example.com' // For remote database
});
`
$3
Collections are similar to tables in SQL or collections in MongoDB:
`javascript
// Get or create a collection
const users = db.collection('users');
const products = db.collection('products');
const orders = db.collection('orders');
`
$3
Documents are JSON objects stored in collections:
`javascript
const userDocument = {
id: 'abc123', // Unique identifier (auto-generated if not provided)
name: 'John Doe',
email: 'john@example.com',
age: 30,
tags: ['admin', 'premium'],
settings: {
notifications: true,
theme: 'dark'
},
createdAt: '2024-01-15T10:30:00Z',
updatedAt: '2024-01-15T10:30:00Z'
};
`
Collection Methods
$3
#### 1. Insert Documents
`javascript
// Insert a single document
const { error, data } = await collection.insert({
name: 'Alice',
email: 'alice@example.com',
age: 25
});
// Insert multiple documents
const { error, data } = await collection.insert([
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' }
]);
// Result: data = {
// insertedCount: 2,
// updatedCount: 0,
// totalDocuments: 5,
// insertedIds: ['abc123', 'def456']
// }
`
#### 2. Find Documents
`javascript
// Find all documents
const { error, data } = await collection.find();
// Find with filters
const { error, data } = await collection.find({
age: { $gt: 18 }
});
// Find with pagination
const { error, data } = await collection.find({}, {
limit: 10,
skip: 20,
sort: { createdAt: -1 } // -1 = descending, 1 = ascending
});
// Find one document
const { error, data } = await collection.findOne({ email: 'alice@example.com' });
// Find by ID
const { error, data } = await collection.findById('document-id-here');
`
#### 3. Update Documents
`javascript
// Update by ID
const { error, data } = await collection.updateById('doc-id', {
name: 'Updated Name',
age: 31
});
// Update multiple documents with filters
const { error, data } = await collection.update(
{ status: 'pending' }, // Filter
{ status: 'completed' }, // Update
{ returnType: 'count' } // Options
);
// Using update operators
const { error, data } = await collection.updateById('doc-id', {
$set: { status: 'active' },
$inc: { loginCount: 1 },
$push: { logs: 'User logged in' }
});
`
#### 4. Delete Documents
`javascript
// Delete by ID
const { error, data } = await collection.deleteById('doc-id');
// Delete with filters
const { error, data } = await collection.delete({
status: 'inactive',
lastLogin: { $lt: '2023-01-01' }
});
// Delete entire collection
const { error, data } = await collection.drop();
`
#### 5. Count Documents
`javascript
// Count all documents
const { error, data } = await collection.count();
// Count with filters
const { error, data } = await collection.count({
status: 'active',
age: { $gte: 18 }
});
`
$3
`javascript
const options = {
sort: { createdAt: -1, name: 1 }, // Sort by multiple fields
limit: 50, // Limit results
skip: 100, // Skip first 100 results
page: 3, // Page number (requires limit)
fields: { name: 1, email: 1 }, // Include only specific fields
returnType: 'documents', // 'count', 'ids', or 'documents'
maxReturn: 1000 // Maximum documents to return
};
const { error, data } = await collection.find({}, options);
`
Query Filters
LiekoDB supports MongoDB-like query syntax:
$3
`javascript
// Equality
{ age: 25 }
{ name: 'John' }
{ status: { $eq: 'active' } }
// Inequality
{ age: { $ne: 25 } }
{ status: { $ne: 'inactive' } }
// Greater than / Less than
{ age: { $gt: 18 } } // Greater than
{ age: { $gte: 21 } } // Greater than or equal
{ age: { $lt: 65 } } // Less than
{ age: { $lte: 100 } } // Less than or equal
// In / Not in
{ role: { $in: ['admin', 'moderator'] } }
{ status: { $nin: ['banned', 'suspended'] } }
// Exists
{ email: { $exists: true } }
{ middleName: { $exists: false } }
// Regular expressions
{ email: { $regex: /@gmail\.com$/ } }
{ name: { $regex: '^J', $options: 'i' } } // Case insensitive
// Modulo
{ age: { $mod: [2, 0] } } // Even numbers
`
$3
`javascript
// AND (default)
{ age: { $gt: 18 }, status: 'active' }
// OR
{ $or: [{ status: 'active' }, { verified: true }] }
// AND with OR
{
$and: [
{ age: { $gte: 18 } },
{
$or: [
{ role: 'admin' },
{ premium: true }
]
}
]
}
// NOT
{ status: { $not: { $eq: 'banned' } } }
{ age: { $not: { $lt: 18 } } }
`
$3
`javascript
// Match array element
{ tags: 'javascript' } // Array contains 'javascript'
{ tags: { $in: ['javascript', 'nodejs'] } } // Array contains any of these
// Array field queries
{ 'skills.level': { $gt: 3 } } // Nested array field
{ 'comments.0.author': 'admin' } // First element of array
`
$3
`javascript
// Dot notation for nested objects
{ 'address.city': 'New York' }
{ 'settings.theme': 'dark' }
{ 'metrics.visits.count': { $gt: 100 } }
`
Examples
$3
`javascript
// todoService.js
const { db } = require('./database');
class TodoService {
constructor() {
this.collection = db.collection('todos');
}
async createTodo(userId, text) {
const { error, data } = await this.collection.insert({
userId,
text,
completed: false,
createdAt: new Date().toISOString(),
priority: 'medium'
});
if (error) throw new Error(error.message);
return data;
}
async getUserTodos(userId, options = {}) {
const { error, data } = await this.collection.find(
{ userId },
{
sort: { createdAt: -1 },
...options
}
);
if (error) throw new Error(error.message);
return data.foundDocuments;
}
async completeTodo(todoId) {
const { error, data } = await this.collection.updateById(todoId, {
completed: true,
completedAt: new Date().toISOString()
});
if (error) throw new Error(error.message);
return data;
}
async deleteCompletedTodos(userId) {
const { error, data } = await this.collection.delete({
userId,
completed: true
});
if (error) throw new Error(error.message);
return data.deletedCount;
}
async getTodoStats(userId) {
const total = await this.collection.count({ userId });
const completed = await this.collection.count({
userId,
completed: true
});
const pending = await this.collection.count({
userId,
completed: false
});
return { total, completed, pending };
}
}
module.exports = new TodoService();
`
$3
`javascript
// blogService.js
const { db } = require('./database');
class BlogService {
constructor() {
this.posts = db.collection('posts');
this.comments = db.collection('comments');
}
async createPost(authorId, title, content, tags = []) {
const { error, data } = await this.posts.insert({
authorId,
title,
content,
tags,
status: 'published',
views: 0,
likes: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
});
if (error) throw new Error(error.message);
return data;
}
async getPosts(options = {}) {
const { error, data } = await this.posts.find(
{ status: 'published' },
{
sort: { createdAt: -1 },
limit: options.limit || 20,
skip: options.skip || 0,
fields: {
title: 1,
excerpt: 1,
authorId: 1,
tags: 1,
createdAt: 1,
views: 1,
likes: 1
}
}
);
if (error) throw new Error(error.message);
return data;
}
async incrementViews(postId) {
const { error, data } = await this.posts.updateById(postId, {
$inc: { views: 1 }
});
if (error) throw new Error(error.message);
return data;
}
async addComment(postId, userId, text) {
const { error, data } = await this.comments.insert({
postId,
userId,
text,
createdAt: new Date().toISOString()
});
if (error) throw new Error(error.message);
return data;
}
async getPostComments(postId) {
const { error, data } = await this.comments.find(
{ postId },
{ sort: { createdAt: -1 } }
);
if (error) throw new Error(error.message);
return data.foundDocuments;
}
async searchPosts(query) {
const { error, data } = await this.posts.find({
$or: [
{ title: { $regex: query, $options: 'i' } },
{ content: { $regex: query, $options: 'i' } },
{ tags: query }
]
});
if (error) throw new Error(error.message);
return data.foundDocuments;
}
}
module.exports = new BlogService();
`
$3
`javascript
// productService.js
const { db } = require('./database');
class ProductService {
constructor() {
this.products = db.collection('products');
this.categories = db.collection('categories');
}
async addProduct(productData) {
const { error, data } = await this.products.insert({
...productData,
sku: this.generateSKU(),
stock: productData.stock || 0,
price: parseFloat(productData.price),
rating: 0,
reviewCount: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
});
if (error) throw new Error(error.message);
return data;
}
async getProducts(filters = {}, options = {}) {
const query = { ...filters, active: true };
const { error, data } = await this.products.find(query, {
sort: options.sort || { createdAt: -1 },
limit: options.limit || 50,
skip: options.skip || 0,
...options
});
if (error) throw new Error(error.message);
return data;
}
async updateStock(productId, quantity) {
const { error, data } = await this.products.updateById(productId, {
$inc: { stock: quantity }
});
if (error) throw new Error(error.message);
return data;
}
async addReview(productId, rating, review) {
// Update product rating (average calculation)
const product = await this.products.findById(productId);
const newRating = (
(product.rating * product.reviewCount + rating) /
(product.reviewCount + 1)
).toFixed(1);
const { error, data } = await this.products.updateById(productId, {
$inc: { reviewCount: 1 },
$set: { rating: parseFloat(newRating) }
});
if (error) throw new Error(error.message);
// Store individual review
await db.collection('reviews').insert({
productId,
rating,
review,
createdAt: new Date().toISOString()
});
return data;
}
async getProductsByCategory(categoryId, options = {}) {
const { error, data } = await this.products.find(
{ categoryId, active: true },
{
sort: { price: options.sortPrice ? 1 : -1 },
limit: options.limit || 20,
...options
}
);
if (error) throw new Error(error.message);
return data.foundDocuments;
}
async searchProducts(searchTerm, filters = {}) {
const query = {
$and: [
{
$or: [
{ name: { $regex: searchTerm, $options: 'i' } },
{ description: { $regex: searchTerm, $options: 'i' } },
{ tags: searchTerm }
]
},
{ ...filters, active: true }
]
};
const { error, data } = await this.products.find(query);
if (error) throw new Error(error.message);
return data.foundDocuments;
}
generateSKU() {
return 'SKU-' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5).toUpperCase();
}
}
module.exports = new ProductService();
`
$3
`javascript
// chatService.js
const { db } = require('./database');
class ChatService {
constructor() {
this.messages = db.collection('messages');
this.rooms = db.collection('chat_rooms');
this.users = db.collection('chat_users');
}
async createRoom(name, creatorId, isPrivate = false) {
const { error, data } = await this.rooms.insert({
name,
creatorId,
isPrivate,
members: [creatorId],
createdAt: new Date().toISOString()
});
if (error) throw new Error(error.message);
return data;
}
async sendMessage(roomId, userId, text) {
const { error, data } = await this.messages.insert({
roomId,
userId,
text,
timestamp: new Date().toISOString(),
readBy: [userId]
});
if (error) throw new Error(error.message);
return data;
}
async getRoomMessages(roomId, options = {}) {
const { error, data } = await this.messages.find(
{ roomId },
{
sort: { timestamp: -1 },
limit: options.limit || 50,
...options
}
);
if (error) throw new Error(error.message);
return data.foundDocuments.reverse(); // Return oldest first
}
async markAsRead(messageId, userId) {
const { error, data } = await this.messages.updateById(messageId, {
$addToSet: { readBy: userId }
});
if (error) throw new Error(error.message);
return data;
}
async getUnreadCount(roomId, userId) {
const { error, data } = await this.messages.count({
roomId,
readBy: { $nin: [userId] }
});
if (error) throw new Error(error.message);
return data;
}
async getUserRooms(userId) {
const { error, data } = await this.rooms.find({
members: userId
});
if (error) throw new Error(error.message);
return data.foundDocuments;
}
async addMemberToRoom(roomId, userId) {
const { error, data } = await this.rooms.updateById(roomId, {
$addToSet: { members: userId }
});
if (error) throw new Error(error.message);
return data;
}
async searchMessages(roomId, searchTerm) {
const { error, data } = await this.messages.find({
roomId,
text: { $regex: searchTerm, $options: 'i' }
});
if (error) throw new Error(error.message);
return data.foundDocuments;
}
}
module.exports = new ChatService();
`
$3
`javascript
// analyticsService.js
const { db } = require('./database');
class AnalyticsService {
constructor() {
this.events = db.collection('analytics_events');
this.sessions = db.collection('user_sessions');
}
async trackEvent(userId, eventType, properties = {}) {
const { error, data } = await this.events.insert({
userId: userId || 'anonymous',
eventType,
properties,
timestamp: new Date().toISOString(),
userAgent: properties.userAgent,
ip: properties.ip,
path: properties.path
});
if (error) throw new Error(error.message);
return data;
}
async startSession(userId, userAgent, ip) {
const sessionId = require('crypto').randomBytes(16).toString('hex');
const { error, data } = await this.sessions.insert({
sessionId,
userId,
userAgent,
ip,
startTime: new Date().toISOString(),
lastActivity: new Date().toISOString(),
events: []
});
if (error) throw new Error(error.message);
return sessionId;
}
async updateSessionActivity(sessionId) {
const { error, data } = await this.sessions.update(
{ sessionId },
{ lastActivity: new Date().toISOString() }
);
if (error) throw new Error(error.message);
return data;
}
async getEventStats(eventType, startDate, endDate) {
const { error, data } = await this.events.count({
eventType,
timestamp: {
$gte: startDate,
$lte: endDate
}
});
if (error) throw new Error(error.message);
return data;
}
async getUserActivity(userId, days = 7) {
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const { error, data } = await this.events.find({
userId,
timestamp: { $gte: startDate.toISOString() }
}, {
sort: { timestamp: -1 },
fields: {
eventType: 1,
timestamp: 1,
'properties.path': 1
}
});
if (error) throw new Error(error.message);
return data.foundDocuments;
}
async getPopularPages(limit = 10) {
// Group by path using multiple queries (simplified)
const allEvents = await this.events.find({}, {
fields: { 'properties.path': 1 },
limit: 10000
});
const pathCounts = {};
allEvents.foundDocuments.forEach(event => {
const path = event.properties?.path;
if (path) {
pathCounts[path] = (pathCounts[path] || 0) + 1;
}
});
return Object.entries(pathCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, limit)
.map(([path, count]) => ({ path, count }));
}
async cleanupOldData(daysToKeep = 90) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
// Delete old events
const eventsResult = await this.events.delete({
timestamp: { $lt: cutoffDate.toISOString() }
});
// Delete old sessions
const sessionsResult = await this.sessions.delete({
lastActivity: { $lt: cutoffDate.toISOString() }
});
return {
deletedEvents: eventsResult.deletedCount || 0,
deletedSessions: sessionsResult.deletedCount || 0
};
}
}
module.exports = new AnalyticsService();
`
Advanced Usage
$3
`javascript
// Get database status
const status = await db.status();
console.log(status);
// {
// storagePath: './storage',
// collections: [...],
// totalCollections: 5,
// totalDocuments: 1234,
// totalCollectionsSize: 1024576,
// totalCollectionsSizeFormatted: '1.02 MB'
// }
// List all collections
const collections = await db.listCollections();
collections.forEach(col => {
console.log(${col.name}: ${col.totalDocuments} docs (${col.sizeFormatted}));
});
// Drop a collection
await db.dropCollection('old_data');
// Close database (flushes all pending writes)
await db.close();
`
$3
`javascript
// Browser usage
const db = new LiekoDB({
token: 'your-api-token',
databaseUrl: 'https://api.yourservice.com'
});
// The API is the same as local usage
const users = db.collection('users');
const { data } = await users.find({ active: true });
`
$3
`javascript
const db = new LiekoDB({
storagePath: './myapp/data',
autoSaveInterval: 30000 // Save every 30 seconds
});
// Or set via environment variable
const db = new LiekoDB({
storagePath: process.env.DB_PATH || './storage'
});
`
$3
`javascript
async function safeDatabaseOperation() {
try {
const { error, data } = await collection.insert(document);
if (error) {
// Handle specific error types
if (error.code === 404) {
console.error('Collection not found');
} else if (error.message.includes('validation')) {
console.error('Validation error:', error.message);
} else {
console.error('Database error:', error);
}
return null;
}
return data;
} catch (err) {
console.error('Unexpected error:', err);
throw err;
}
}
`
$3
`javascript
const db = new LiekoDB({
autoSaveInterval: 30000, // Longer interval for write-heavy apps
debug: false // Disable debug in production
});
// Use projection to reduce data transfer
const { data } = await collection.find({}, {
fields: { name: 1, email: 1 }, // Only get these fields
limit: 100
});
// Use count instead of find when only need count
const { data: count } = await collection.count(filters);
// Use pagination for large datasets
async function getAllPaginated(collection, batchSize = 1000) {
let allDocuments = [];
let skip = 0;
let hasMore = true;
while (hasMore) {
const { data } = await collection.find({}, {
limit: batchSize,
skip: skip
});
if (data.foundDocuments.length === 0) {
hasMore = false;
} else {
allDocuments = allDocuments.concat(data.foundDocuments);
skip += batchSize;
}
}
return allDocuments;
}
`
API Reference
$3
`javascript
new LiekoDB(options)
`
Options:
- debug (boolean): Enable debug logging (default: false)
- autoSaveInterval (number): Auto-save interval in ms (default: 5000)
- storagePath (string): Path for file storage (default: './storage')
- token (string): Authentication token for HTTP adapter
- databaseUrl (string): URL for remote database
- poolSize (number): HTTP connection pool size (default: 10)
- maxRetries (number): HTTP retry attempts (default: 3)
- timeout (number): HTTP timeout in ms (default: 15000)
$3
All methods return a Promise resolving to { error?, data? }
#### insert(documents)
Insert one or multiple documents.
#### find(filters?, options?)
Find documents matching filters.
#### findOne(filters?, options?)
Find a single document.
#### findById(id, options?)
Find document by ID.
#### update(filters, update, options?)
Update multiple documents.
#### updateById(id, update, options?)
Update document by ID.
#### delete(filters)
Delete documents matching filters.
#### deleteById(id)
Delete document by ID.
#### count(filters?)
Count documents matching filters.
#### drop()
Delete entire collection.
$3
#### Comparison
- $eq - Equal
- $ne - Not equal
- $gt - Greater than
- $gte - Greater than or equal
- $lt - Less than
- $lte - Less than or equal
- $in - In array
- $nin - Not in array
- $exists - Field exists
- $regex - Regular expression
- $mod - Modulo operation
#### Logical
- $and - Logical AND
- $or - Logical OR
- $not - Logical NOT
- $nor - Logical NOR
$3
- $set - Set field value
- $unset - Remove field
- $inc - Increment field
- $push - Append to array
- $pull - Remove from array
- $addToSet - Add to array if not exists
Best Practices
$3
`javascript
async function safeOperation() {
const { error, data } = await collection.operation();
if (error) {
// Handle appropriately
throw new Error(Database error: ${error.message});
}
return data;
}
`
$3
While LiekoDB doesn't have traditional indexes, you can:
- Keep collections small and focused
- Use meaningful IDs for quick lookups
- Consider splitting large collections
$3
`javascript
class UserService {
async createUser(userData) {
// Validate before inserting
if (!userData.email || !userData.email.includes('@')) {
throw new Error('Invalid email');
}
const { error, data } = await this.collection.insert({
...userData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
});
return data;
}
}
`
$3
`javascript
// For read-heavy: Larger auto-save interval
const readHeavyDB = new LiekoDB({
autoSaveInterval: 60000 // 1 minute
});
// For write-heavy: Smaller auto-save interval
const writeHeavyDB = new LiekoDB({
autoSaveInterval: 1000 // 1 second
});
`
$3
`javascript
// Simple backup function
async function backupDatabase(sourcePath, backupPath) {
const fs = require('fs').promises;
try {
const files = await fs.readdir(sourcePath);
for (const file of files) {
if (file.endsWith('.json')) {
const source = ${sourcePath}/${file};
const backup = ${backupPath}/${file}.${Date.now()}.bak;
await fs.copyFile(source, backup);
}
}
console.log('Backup completed successfully');
} catch (error) {
console.error('Backup failed:', error);
}
}
`
$3
`javascript
// Add performance logging
const db = new LiekoDB({
debug: process.env.NODE_ENV === 'development'
});
// Log slow queries
const start = Date.now();
const { data } = await collection.find(complexQuery);
const duration = Date.now() - start;
if (duration > 1000) { // 1 second
console.warn(Slow query: ${duration}ms);
}
`
Troubleshooting
$3
1. "Collection name contains invalid characters"
- Use only alphanumeric characters, underscores, and hyphens
- Cannot start with numbers
2. Data not persisting
- Check autoSaveInterval setting
- Call db.close() before exiting
- Check file permissions
3. Slow queries
- Use projection to limit returned fields
- Implement pagination
- Consider splitting large collections
4. Memory usage
- Monitor cache size for large collections
- Use limit in queries
- Consider disabling auto-save for read-only operations
$3
Enable debug mode to see detailed logs:
`javascript
const db = new LiekoDB({ debug: true });
``