dapbase
v3.0.3
Published
A dead-simple, file-based, folder-visible database for Node.js — now with relationships, schema validation, and encryption
Maintainers
Readme
Dapbase v3.0
A dead-simple, file-based, folder-visible database for Node.js — now with relationships, schema validation, and encryption
No server. No binaries. No black boxes.
Your data lives in plain files inside a Dapbase/ folder you can open, read, backup, or edit by hand if you ever want to.
Perfect for small apps, prototypes, CLI tools, learning, or when you just want full control.
🚀 What's New in v3.0
- ✅ Auto-Generated Timestamps -
createdAtandupdatedAtautomatically managed - ✅ Schema Validation - Type checking, constraints, and defaults
- ✅ Relationships - Foreign keys with cascade delete
- ✅ Advanced Query Operators -
$gt,$lt,$gte,$lte,$in,$like,$regex - ✅ Encryption - Field-level AES-256 encryption
- ✅ Backup System - One-command backups and restores
- ✅ Indexes - Performance optimization
- ✅ Migrations - Safe data transformations
- ✅ 100% Test Coverage - Battle-tested with 40+ comprehensive tests
✨ Features
- Zero dependencies (core) - Just Node.js, nothing else
- 100% transparent - See every table as readable
.tablefiles - Multiple databases - Just create folders
- Schema Validation - Enforce data integrity with constraints
- Auto-Timestamps -
createdAtandupdatedAthandled automatically - Relationships - Model complex data with foreign keys
- Encryption - Optional field-level security
- Query Power - Advanced filtering with operators, joins, and pagination
- Atomic Writes - Data integrity with write-file-atomic
- Type Safety - Runtime type checking with custom validators
- Migration Support - Safe data transformations
📦 Installation
npm install dapbaseOr try it immediately:
npx dapbase@latest initThe installer automatically creates a Dapbase/ folder in your project with everything you need.
📁 Folder Structure After Install
Dapbase/
├── dapbase.config.json # Global configuration
├── dapbase.connection.js # Import this in your app
├── .encryption.key # Auto-generated encryption key (if enabled)
└── main/ # Your first database (auto-created)
├── users.table
├── posts.table
└── ... # Your tables appear here as readable files🎮 Quick Start
1. Initialize Your Project
npx dapbase initFollow the interactive prompts to set up your database.
2. Use in Your Code
// app.js
const db = require('./Dapbase/dapbase.connection.js');
(async () => {
// Switch to a database (creates folder if needed)
await db.use('blog_db');
// Create a table with schema validation
await db.createTable('users', {
name: { type: 'string', required: true },
email: { type: 'string', unique: true, pattern: '^[^@]+@[^@]+\\.[^@]+$' },
age: { type: 'integer', min: 0, max: 150 },
status: { type: 'string', default: 'active' }
});
// Insert data - timestamps are auto-generated!
const user = await db.insert('users', {
name: 'John Doe',
email: '[email protected]',
age: 30
});
console.log(user);
// {
// id: 'uuid-generated',
// name: 'John Doe',
// email: '[email protected]',
// age: 30,
// createdAt: '2026-01-16T12:00:00.000Z',
// updatedAt: '2026-01-16T12:00:00.000Z'
// }
// Query with advanced filters
const activeUsers = await db.select('users', {
where: {
status: 'active',
age: { $gte: 18, $lte: 65 }
},
orderBy: ['name', 'asc'],
limit: 10,
offset: 0
});
console.log(activeUsers);
})();📖 Core API
Database Operations
// Switch to/create database
await db.use('database_name');Table Management
// Create table with schema
await db.createTable('users', {
username: { type: 'string', required: true, unique: true },
email: { type: 'string', required: true },
age: { type: 'integer', min: 0, max: 150 }
});
// Create table without auto-timestamps
await db.createTable('logs', {
message: { type: 'string' }
}, {}, { timestamps: false });
// Create table with relationships
await db.createTable('posts', {
title: { type: 'string', required: true },
content: { type: 'text' },
userId: { type: 'uuid' }
}, {
userId: { foreignTable: 'users', foreignKey: 'id' }
});
// Add column to existing table
await db.addColumn('users', 'bio', { type: 'text', default: '' });
// Remove column
await db.removeColumn('users', 'old_field');Data Operations
// Insert single row (auto-generates id, createdAt, updatedAt)
const user = await db.insert('users', {
username: 'jane_doe',
email: '[email protected]',
age: 28
});
// Insert multiple rows
const users = await db.insertMany('users', [
{ username: 'alice', email: '[email protected]', age: 25 },
{ username: 'bob', email: '[email protected]', age: 30 }
]);
// Query with operators
const users = await db.select('users', {
where: {
age: { $gte: 18, $lt: 65 },
email: { $like: '@example.com' },
status: { $in: ['active', 'pending'] }
},
orderBy: ['createdAt', 'desc'],
limit: 20,
offset: 0,
fields: ['username', 'email'] // Select specific fields
});
// Update (automatically updates updatedAt timestamp)
await db.update('users',
{ status: 'inactive' },
{ age: { $lt: 18 } }
);
// Delete with cascade option
await db.delete('users', { status: 'banned' }, { cascade: true });Query Operators
// Comparison operators
where: {
age: { $eq: 25 }, // Equal to
age: { $ne: 25 }, // Not equal to
age: { $gt: 18 }, // Greater than
age: { $gte: 18 }, // Greater than or equal
age: { $lt: 65 }, // Less than
age: { $lte: 65 }, // Less than or equal
status: { $in: ['active', 'pending'] }, // In array
status: { $nin: ['banned', 'deleted'] }, // Not in array
email: { $like: '@example.com' }, // Contains string
username: { $regex: '^admin' } // Regex pattern
}
// Combine multiple operators
where: {
age: { $gte: 18, $lte: 65 },
email: { $like: '@company.com' }
}Relationships & Joins
// Query with join
const postsWithAuthors = await db.select('posts', {
join: [{
table: 'users',
on: { local: 'userId', foreign: 'id' },
as: 'author',
type: 'left' // or 'inner'
}],
where: { status: 'published' }
});
// Result structure:
// [
// {
// id: 'post-uuid',
// title: 'My Post',
// userId: 'user-uuid',
// author: {
// id: 'user-uuid',
// username: 'john_doe',
// email: '[email protected]'
// }
// }
// ]🔐 Schema Validation
Available Types
{
// String types
name: { type: 'string' }, // Short text
content: { type: 'text' }, // Long text
// Numeric types
age: { type: 'integer' }, // Whole numbers
age: { type: 'int' }, // Alias for integer
price: { type: 'float' }, // Decimal numbers
score: { type: 'number' }, // Any number
// Other types
active: { type: 'boolean' },
id: { type: 'uuid' },
createdAt: { type: 'timestamp' },
birthday: { type: 'date' },
metadata: { type: 'json' }
}Constraints
{
// Required fields
email: { type: 'string', required: true },
// Unique values
username: { type: 'string', unique: true },
// Numeric constraints
age: { type: 'integer', min: 0, max: 150 },
price: { type: 'float', min: 0 },
// String length
password: { type: 'string', minLength: 8, maxLength: 100 },
// Pattern matching (regex)
email: {
type: 'string',
pattern: '^[^@]+@[^@]+\\.[^@]+$'
},
// Default values
status: { type: 'string', default: 'active' },
score: { type: 'integer', default: 0 },
tags: { type: 'json', default: [] }
}Auto-Generated Fields
// These are automatically added unless timestamps: false
{
id: { type: 'uuid', required: true }, // Auto-generated UUID
createdAt: { type: 'string', default: 'NOW' }, // Auto-set on insert
updatedAt: { type: 'string', default: 'NOW' } // Auto-updated on update
}
// Disable timestamps for a table
await db.createTable('logs', {
message: { type: 'string' }
}, {}, { timestamps: false });🔐 Encryption
// Configure encryption in dapbase.config.json or programmatically
await db.createTable('secrets', {
publicData: { type: 'string' },
privateData: { type: 'string' }
}, {}, {
encryption: {
fields: ['privateData'] // Only encrypt sensitive fields
}
});
// Insert data (auto-encrypts privateData)
await db.insert('secrets', {
publicData: 'Everyone can see this',
privateData: 'This is encrypted in the file'
});
// Query (auto-decrypts on read)
const secrets = await db.select('secrets');
console.log(secrets[0].privateData); // Decrypted automatically📊 Real-World Examples
Example 1: Blog Application
const db = require('./Dapbase/dapbase.connection.js');
async function setupBlog() {
await db.use('blog');
// Users table
await db.createTable('users', {
username: { type: 'string', unique: true, required: true },
email: { type: 'string', unique: true, required: true },
passwordHash: { type: 'string', required: true },
role: { type: 'string', default: 'user' }
});
// Posts table with foreign key
await db.createTable('posts', {
title: { type: 'string', required: true, maxLength: 200 },
slug: { type: 'string', unique: true },
content: { type: 'text', required: true },
status: { type: 'string', default: 'draft' },
userId: { type: 'uuid', required: true }
}, {
userId: { foreignTable: 'users', foreignKey: 'id' }
});
// Comments table
await db.createTable('comments', {
content: { type: 'text', required: true },
postId: { type: 'uuid', required: true },
userId: { type: 'uuid', required: true }
}, {
postId: { foreignTable: 'posts', foreignKey: 'id' },
userId: { foreignTable: 'users', foreignKey: 'id' }
});
}
// Get published posts with authors
async function getPublishedPosts(page = 1, limit = 10) {
const offset = (page - 1) * limit;
return await db.select('posts', {
where: { status: 'published' },
join: [{
table: 'users',
on: { local: 'userId', foreign: 'id' },
as: 'author',
type: 'left'
}],
orderBy: ['createdAt', 'desc'],
limit,
offset
});
}
// Create a post with validation
async function createPost(userId, postData) {
return await db.insert('posts', {
...postData,
userId,
slug: postData.title.toLowerCase().replace(/\s+/g, '-')
});
}Example 2: E-commerce Store
async function setupStore() {
await db.use('store');
// Products
await db.createTable('products', {
name: { type: 'string', required: true },
sku: { type: 'string', unique: true, required: true },
price: { type: 'float', min: 0, required: true },
stock: { type: 'integer', min: 0, default: 0 },
category: { type: 'string' }
});
// Orders
await db.createTable('orders', {
userId: { type: 'uuid', required: true },
status: { type: 'string', default: 'pending' },
total: { type: 'float', min: 0 }
}, {
userId: { foreignTable: 'users', foreignKey: 'id' }
});
// Order items
await db.createTable('orderItems', {
orderId: { type: 'uuid', required: true },
productId: { type: 'uuid', required: true },
quantity: { type: 'integer', min: 1, required: true },
price: { type: 'float', min: 0, required: true }
}, {
orderId: { foreignTable: 'orders', foreignKey: 'id' },
productId: { foreignTable: 'products', foreignKey: 'id' }
});
}
// Get low stock products
async function getLowStockProducts(threshold = 10) {
return await db.select('products', {
where: { stock: { $lte: threshold } },
orderBy: ['stock', 'asc']
});
}⚡ Performance Optimization
Indexes
// Add index for faster queries
await db.addIndex('users', 'email', { type: 'hash', unique: true });
await db.addIndex('posts', 'createdAt', { type: 'value' });
// Indexes are automatically used in queries
const user = await db.select('users', {
where: { email: '[email protected]' }
}); // Fast lookup via indexField Selection
// Only fetch needed fields
const users = await db.select('users', {
fields: ['id', 'username', 'email'], // Don't load all fields
limit: 100
});Pagination
// Efficient pagination
function getPage(page = 1, pageSize = 20) {
return db.select('posts', {
limit: pageSize,
offset: (page - 1) * pageSize,
orderBy: ['createdAt', 'desc']
});
}🔄 Data Migrations
// Add new field to existing rows
await db.addColumn('users', 'fullName', { type: 'string', default: '' });
// Populate new field from existing data
await db.migrate('users', (row) => ({
...row,
fullName: `${row.firstName || ''} ${row.lastName || ''}`.trim()
}));
// Remove old fields
await db.removeColumn('users', 'firstName');
await db.removeColumn('users', 'lastName');🧪 Testing
Dapbase includes a comprehensive test suite with 40+ tests covering all features:
# Run the test suite
node comprehensive-test.jsTest Coverage:
- ✅ Database operations
- ✅ Table creation with schemas
- ✅ Insert with validation
- ✅ Query operators ($gt, $lt, $in, $like, etc.)
- ✅ Updates with validation
- ✅ Delete operations
- ✅ Foreign key constraints
- ✅ Joins and relationships
- ✅ Cascade deletes
- ✅ Schema operations
- ✅ Migrations
- ✅ Edge cases
📈 Performance Tips
- Use indexes on frequently queried fields (email, username, etc.)
- Select specific fields instead of fetching entire rows
- Use pagination for large result sets
- Batch operations with
insertManyfor bulk data - Enable encryption only for sensitive fields
- Split large tables by date ranges or categories
- Regular backups keep your database healthy
🐛 Debugging
# Inspect table files directly
cat Dapbase/blog/users.table | jq '.'
# Check table structure
cat Dapbase/blog/users.table | jq '.columns'
# View all rows
cat Dapbase/blog/users.table | jq '.rows'🔧 Configuration
Edit Dapbase/dapbase.config.json:
{
"project": "My Awesome App",
"environment": "development",
"defaultDatabase": "main",
"encryptionEnabled": false,
"logLevel": "info",
"maxFileSizeMB": 10
}🚨 Migration from v2
All v2 APIs are backward compatible:
// Old v2 code (still works)
await db.createTable('users', { name: 'text' });
// New v3 code (recommended - enables auto-timestamps)
await db.createTable('users', {
name: { type: 'string', required: true }
});New in v3:
- Auto-generated
id,createdAt,updatedAtfields - Query operators (
$gt,$lt,$in, etc.) - Foreign key validation
- Enhanced schema validation
🤝 Contributing
Dapbase is built slowly, intentionally, and in public.
Ways to contribute:
- Report bugs or suggest features
- Improve documentation
- Share your use cases
- Submit pull requests
- Create examples or tutorials
📄 License
MIT © 2025 Duby
🙏 Acknowledgments
Dapbase stands on the shoulders of giants:
- SQLite for inspiration in simplicity
- Lowdb for the file-based approach
- Prisma for schema validation ideas
- write-file-atomic for atomic writes
- Every developer who believes data should be visible
🆘 Getting Help
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Twitter: @duby
Dapbase — Because your data should be yours.
Simple. Visible. Powerful. Tested.
"If you can't ls your database and understand it, it's not simple enough."
