blitzdb
v3.0.0
Published
A NoSQL, MongoDB-styled in-memory database with persistent JSON storage, full query engine, aggregation pipeline, permissions model, and browser (localStorage) support.
Maintainers
Readme
BlitzDB
A NoSQL, MongoDB-styled database with a full query engine, aggregation pipeline, permissions model, and persistent JSON storage — for both Node.js and the browser (localStorage).
- Zero external dependencies in the browser build
- MongoDB-compatible query operators and update operators
- Aggregation pipeline (
$match,$group,$sort,$lookup,$unwind, …) - Permission model — Firestore-style rules, disabled by default
- Multi-database support via
BlitzDBmanager orDatabaseclass - CJS + ESM — works with
require()andimport - Browser build — persists to
localStorage; UMD + ESM variants
Table of Contents
- Installation
- Quick Start
- Imports & Module Formats
- Database Management
- Collections
- Query Operators
- Update Operators
- Aggregation Pipeline
- Indexes
- Validators
- Permission Model
- Browser Usage
- API Reference
Installation
npm install blitzdbQuick Start
// CJS
const db = require("blitzdb");
// Insert documents
db.users.insertMany([
{ name: "Alice", age: 30, role: "admin" },
{ name: "Bob", age: 25, role: "user" },
]);
// Query with operators
const admins = db.users.find({ age: { $gte: 28 } }).toArray();
// Update
db.users.updateOne({ name: "Bob" }, { $inc: { age: 1 } });
// Delete
db.users.deleteOne({ name: "Bob" });Imports & Module Formats
CommonJS (Node.js)
const db = require("blitzdb"); // default db instance
const { Database, BlitzDB } = require("blitzdb"); // classes
const { PermissionModel, Cursor } = require("blitzdb");ES Modules (Node.js)
import db from "blitzdb";
import { Database, BlitzDB, PermissionModel } from "blitzdb";Browser — UMD (script tag)
<script src="node_modules/blitzdb/browser.js"></script>
<script>
const db = BlitzDB.default;
const { Database, BlitzDB: Manager } = BlitzDB;
</script>Browser — ESM
import db, { Database, BlitzDB } from "blitzdb/browser";
// or
import db from "./node_modules/blitzdb/browser.mjs";Sub-path imports (explicit environment)
import db from "blitzdb/browser"; // always browser build
import db from "blitzdb/node"; // always Node.js buildDatabase Management
Default Database
The default export is a ready-to-use Database instance stored in _blitzdb/. Collections are auto-created on property access.
const db = require("blitzdb");
db.users.insert({ name: "Alice" }); // auto-creates "users" collection
db.posts.insert({ title: "Hello" }); // auto-creates "posts" collectionMultiple Databases
Use the Database class directly or the BlitzDB manager.
Database class
const { Database } = require("blitzdb");
const appDb = new Database("myapp"); // stored in ./_myapp/
const testDb = new Database("test", {
baseDir: "/tmp/databases" // override storage directory
});
appDb.users.insert({ name: "Alice" });
testDb.logs.insert({ level: "info", msg: "started" });BlitzDB manager
const { BlitzDB } = require("blitzdb");
const manager = new BlitzDB({ baseDir: "./data" });
const appDb = manager.db("myapp");
const logsDb = manager.db("logs");
console.log(manager.listDatabases()); // ["myapp", "logs"]
console.log(manager.countDatabases()); // 2
console.log(manager.isDatabaseExists("myapp")); // true
manager.dropDatabase("logs");Database API
| Method | Description |
|---|---|
| db.collection(name) | Get or create a collection (safe alternative to property access) |
| db.dropCollection(name) | Drop a collection and delete its file |
| db.renameCollection(old, new) | Rename a collection |
| db.listCollections() | Returns string[] of collection names |
| db.isCollectionExists(name) | Returns boolean |
| db.countCollections() | Returns total collection count |
| db.stats() | { id, dir, collections, totalDocs, totalSize } |
| db.dropDatabase() | Drop all collections and delete the database directory |
| db.permissions | Access the PermissionModel for this database |
Collections
Insert
// Insert one document — returns the stored document with `_id`
db.users.insertOne({ name: "Alice", age: 30 });
// Alias
db.users.insert({ name: "Bob" });
// Insert multiple — returns array of stored documents
db.users.insertMany([
{ name: "Carol", age: 25 },
{ name: "Dave", age: 28 },
]);Documents receive a UUID _id unless you supply one:
db.users.insert({ _id: "custom-id-001", name: "Eve" });Find / Query
// Find all
db.users.find().toArray();
// Find with query
db.users.find({ role: "admin" }).toArray();
// Projection (inclusion)
db.users.find({}, { name: 1, age: 1 }).toArray();
// Projection (exclusion)
db.users.find({}, { password: 0 }).toArray();
// Find one
db.users.findOne({ name: "Alice" });
// Find by _id
db.users.findById("uuid-here");Cursor Methods
find() returns a Cursor. Chain any of the following before calling .toArray():
db.users
.find({ role: "admin" })
.sort({ age: -1 }) // sort descending by age
.skip(10) // skip 10 documents
.limit(5) // return at most 5
.project({ name: 1 }) // apply projection
.toArray(); // materialise
// Other cursor methods
cursor.count(); // number of matching docs after skip/limit
cursor.first(); // first document or null
cursor.forEach(doc => {}); // iterate
cursor.map(doc => doc.name); // transform
for (const doc of cursor) {} // iterableCount & Distinct
db.users.count(); // all documents
db.users.count({ role: "admin" }); // with query
db.users.countDocuments({ age: { $gt: 18 } });
db.users.estimatedDocumentCount(); // fast, no query
db.users.distinct("role"); // ["admin", "user"]
db.users.distinct("role", { age: { $gt: 20 } });Pagination
const result = db.users.paginate(
{ role: "admin" }, // query
{
page: 1, // 1-indexed
pageSize: 10,
sort: { name: 1 },
projection: { password: 0 },
}
);
// result shape:
// { data: [...], total: 42, page: 1, pageSize: 10, totalPages: 5 }Update
updateOne(query, update, options?)
Updates the first matching document. Returns { updated, upserted }.
db.users.updateOne(
{ name: "Alice" },
{ $set: { age: 31 }, $push: { tags: "admin" } }
);
// Upsert — insert if not found
db.users.updateOne(
{ name: "Zed" },
{ $set: { age: 20 } },
{ upsert: true }
);updateMany(query, update)
Updates all matching documents. Returns array of updated documents.
db.users.updateMany({ role: "user" }, { $inc: { loginCount: 1 } });replaceOne(query, replacement, options?)
Replaces the entire document (keeps _id). Returns { updated, upserted }.
db.users.replaceOne({ name: "Bob" }, { name: "Bob", age: 26, role: "moderator" });Delete
// Delete first matching document — returns the deleted doc
db.users.deleteOne({ name: "Bob" });
// Delete all matching — returns array of deleted docs
db.users.deleteMany({ role: "guest" });
// Delete all (empty query)
db.users.deleteMany({});Find-and-Modify
// Returns doc AFTER update by default
db.users.findOneAndUpdate({ name: "Alice" }, { $inc: { age: 1 } });
// Return doc BEFORE update
db.users.findOneAndUpdate(
{ name: "Alice" },
{ $set: { online: true } },
{ returnDocument: "before" }
);
db.users.findOneAndReplace({ name: "Alice" }, { name: "Alice", age: 99 });
db.users.findOneAndDelete({ name: "Alice" }); // returns deleted docBulk Write
db.users.bulkWrite([
{ insertOne: { document: { name: "Eve", age: 22 } } },
{ updateOne: { filter: { name: "Alice" }, update: { $set: { age: 31 } } } },
{ updateMany: { filter: { role: "user" }, update: { $inc: { loginCount: 1 } } } },
{ replaceOne: { filter: { name: "Bob" }, replacement: { name: "Bob", age: 26 } } },
{ deleteOne: { filter: { name: "Eve" } } },
{ deleteMany: { filter: { role: "guest" } } },
]);
// Returns: [{ op, result }, ...]Collection Utilities
// Check if document exists by _id
db.users.isDocumentExists("some-uuid"); // boolean
// Stats
db.users.stats();
// { name, count, size (bytes), indexes: [...] }
// Drop collection (delete all data + file)
db.users.drop();
// Rename collection
db.users.rename("members");Query Operators
Comparison
| Operator | Description | Example |
|---|---|---|
| $eq | Equal | { age: { $eq: 30 } } |
| $ne | Not equal | { role: { $ne: "guest" } } |
| $gt | Greater than | { age: { $gt: 18 } } |
| $gte | Greater than or equal | { age: { $gte: 18 } } |
| $lt | Less than | { score: { $lt: 100 } } |
| $lte | Less than or equal | { score: { $lte: 100 } } |
| $in | In array | { role: { $in: ["admin","mod"] } } |
| $nin | Not in array | { role: { $nin: ["guest"] } } |
Logical
// $and — all conditions must match
db.users.find({ $and: [{ age: { $gte: 18 } }, { role: "admin" }] });
// $or — any condition must match
db.users.find({ $or: [{ role: "admin" }, { role: "mod" }] });
// $nor — no condition must match
db.users.find({ $nor: [{ role: "guest" }] });
// $not — negates a condition
db.users.find({ age: { $not: { $lt: 18 } } });Array
| Operator | Description |
|---|---|
| $in | Field value is in given array |
| $all | Array field contains all given values |
| $size | Array field has exactly N elements |
| $elemMatch | At least one array element matches a sub-query |
db.posts.find({ tags: { $all: ["js", "node"] } });
db.posts.find({ comments: { $size: 3 } });
db.posts.find({ scores: { $elemMatch: { $gte: 90 } } });Element
db.users.find({ email: { $exists: true } });
db.users.find({ age: { $exists: false } });
db.users.find({ name: { $type: "string" } });Evaluation
// $regex
db.users.find({ name: { $regex: "^Al", $options: "i" } });
// $mod — modulo
db.items.find({ qty: { $mod: [4, 0] } }); // qty divisible by 4Dot-notation (nested fields)
All operators work with dot-notation paths:
db.users.find({ "address.city": "Mumbai" });
db.users.find({ "profile.age": { $gte: 18 } });Update Operators
Field
| Operator | Description | Example |
|---|---|---|
| $set | Set field value | { $set: { age: 31 } } |
| $unset | Remove a field | { $unset: { tempToken: "" } } |
| $inc | Increment by N | { $inc: { views: 1 } } |
| $mul | Multiply by N | { $mul: { price: 1.1 } } |
| $min | Set if new value is smaller | { $min: { score: 50 } } |
| $max | Set if new value is larger | { $max: { score: 95 } } |
| $rename | Rename a field | { $rename: { oldName: "newName" } } |
Array
| Operator | Description |
|---|---|
| $push | Append value to array |
| $push + $each | Append multiple values |
| $push + $sort | Push and sort |
| $push + $slice | Push and trim to N elements |
| $push + $position | Insert at position |
| $pull | Remove elements matching value or query |
| $pullAll | Remove all occurrences of listed values |
| $addToSet | Push only if not already present |
| $addToSet + $each | Add multiple unique values |
| $pop | Remove first (-1) or last (1) element |
// $push with modifiers
db.leaderboard.updateOne({ name: "Alice" }, {
$push: {
scores: {
$each: [95, 87, 100],
$sort: -1, // sort descending
$slice: 5, // keep top 5
$position: 0, // insert at start (before sort)
}
}
});
// $pull with sub-query
db.users.updateOne({ name: "Alice" }, {
$pull: { tags: { $in: ["temp", "draft"] } }
});
// $addToSet
db.users.updateOne({ name: "Bob" }, {
$addToSet: { roles: { $each: ["editor", "viewer"] } }
});Bitwise
db.flags.updateOne({ name: "mask" }, {
$bit: { flags: { and: 0b1100, or: 0b0001, xor: 0b0010 } }
});Aggregation Pipeline
collection.aggregate(pipeline) accepts an array of stage objects and returns a plain array of documents.
const results = db.orders.aggregate([
{ $match: { status: "complete" } },
{ $group: { _id: "$userId", total: { $sum: "$amount" }, count: { $sum: 1 } } },
{ $sort: { total: -1 } },
{ $limit: 10 },
{ $project: { _id: 1, total: 1 } },
]);Supported Stages
| Stage | Description |
|---|---|
| $match | Filter documents by query |
| $project | Include/exclude/rename fields |
| $sort | Sort documents |
| $limit | Limit document count |
| $skip | Skip N documents |
| $count | Count documents into a named field |
| $group | Group and accumulate |
| $unwind | Deconstruct an array field |
| $lookup | Left join another collection |
| $addFields | Add computed fields |
| $replaceRoot | Replace root document |
| $sample | Random N documents |
$group Accumulators
| Accumulator | Description |
|---|---|
| $sum | Sum of values (or 1 for count) |
| $avg | Average |
| $min | Minimum value |
| $max | Maximum value |
| $push | Collect values into array |
| $addToSet | Collect unique values |
| $first | First value in group |
| $last | Last value in group |
db.sales.aggregate([
{ $group: {
_id: "$category",
revenue: { $sum: "$amount" },
avgPrice: { $avg: "$price" },
minPrice: { $min: "$price" },
items: { $push: "$name" },
uniqueTags: { $addToSet: "$tag" },
}},
{ $sort: { revenue: -1 } },
]);$lookup (join)
db.orders.aggregate([
{ $lookup: {
from: "users", // collection name
localField: "userId",
foreignField: "_id",
as: "user",
}},
]);$unwind
db.posts.aggregate([
{ $unwind: "$tags" }, // simple
{ $unwind: {
path: "$comments",
preserveNullAndEmptyArrays: true,
}},
]);Indexes
// Create index
db.users.createIndex("email", { unique: true });
db.users.createIndex("role");
db.users.createIndex("optionalField", { sparse: true }); // skip docs missing the field
// List indexes
db.users.listIndexes();
// [{ field: "email", unique: true, sparse: false }, ...]
// Drop index
db.users.dropIndex("role");A unique index throws Error: [BlitzDB] Unique index violation … on insert/update that would create a duplicate.
Validators
Add validation functions to a collection. Return true (or undefined) to pass, or a string to fail with that message.
db.users.addValidator(doc => {
if (!doc.name) return "name is required";
if (doc.age < 0) return "age must be non-negative";
if (!doc.email?.includes("@")) return "invalid email";
return true; // or just return nothing
});
db.users.insert({ name: "Alice", age: -1, email: "bad" });
// throws: [BlitzDB] Validation failed: age must be non-negativeMultiple validators can be chained:
db.products
.addValidator(doc => doc.price >= 0 || "price must be ≥ 0")
.addValidator(doc => doc.name?.length || "name must not be empty");Permission Model
By default the permission model is disabled (all operations allowed). Enable it and add rules to restrict access, similar to Firestore Security Rules.
const db = new Database("myapp");
const { permissions } = db;
permissions.enable();
// Allow/deny by collection and action
permissions.allow("*", "*"); // allow everything (default-open pattern)
permissions.deny("admin", "*"); // deny all on "admin"
permissions.allow("admin", "read"); // but allow reads on "admin"
// Conditional rules — doc & context are passed to the condition function
permissions.setContext({ userId: "user-123", role: "user" });
permissions.deny("orders", "delete", (doc, ctx) => {
return ctx.role !== "admin"; // deny delete unless user is admin
});
permissions.allow("posts", "write", (doc, ctx) => {
return doc?.authorId === ctx.userId; // only allow writing your own posts
});Rule evaluation
Rules are evaluated in order. The last matching rule wins. If no rule matches, the operation is allowed (open by default).
Actions
| Action | Covers |
|---|---|
| "read" | find, findOne, findById, count, distinct, paginate, aggregate |
| "insert" / "write" | insert, insertOne, insertMany |
| "update" / "write" | updateOne, updateMany, replaceOne, findOneAndUpdate, findOneAndReplace |
| "delete" / "write" | deleteOne, deleteMany, findOneAndDelete, drop |
| "*" | All actions |
Permission API
permissions.enable() // enable the model
permissions.disable() // disable (allow all)
permissions.setContext(obj) // set ctx passed to condition functions
permissions.allow(collection, action, cond?) // add an allow rule
permissions.deny(collection, action, cond?) // add a deny rule
permissions.addRule({ collection, action, condition, allow }) // low-level
permissions.check(collection, action, doc?) // returns booleanBrowser Usage
The browser build uses localStorage instead of the filesystem. The API is identical to the Node.js version.
Storage keys follow the pattern blitzdb:<dbId>:<collectionName>.
<!-- UMD -->
<script src="browser.js"></script>
<script>
const db = BlitzDB.default;
db.notes.insert({ title: "Hello", body: "World" });
const notes = db.notes.find().toArray();
console.log(notes);
// Multiple databases
const { Database } = BlitzDB;
const userDb = new Database("user-data");
userDb.preferences.insert({ theme: "dark" });
</script>// ESM
import db, { Database, BlitzDB } from "./browser.mjs";
db.todos.insertMany([
{ text: "Buy milk", done: false },
{ text: "Write code", done: true },
]);
const pending = db.todos.find({ done: false }).toArray();Custom storage adapter
Provide any object with getItem / setItem / removeItem / key / length before loading BlitzDB:
globalThis._blitzStorage = myCustomStorageAdapter;API Reference
Database / BlitzDB manager
| Method | Returns | Description |
|---|---|---|
| new Database(id, options?) | Database | Create/open a database. Folder: _<id> |
| new BlitzDB(options?) | BlitzDB | Create a manager |
| manager.db(id) | Database | Get/create database |
| manager.listDatabases() | string[] | All database IDs on disk |
| manager.isDatabaseExists(id) | boolean | — |
| manager.countDatabases() | number | — |
| manager.dropDatabase(id) | void | Delete database and directory |
| manager.stats() | object[] | Stats per database |
Collection
| Method | Returns | Description |
|---|---|---|
| insert(doc) | doc | Insert one document |
| insertOne(doc) | doc | Alias for insert |
| insertMany(docs) | doc[] | Insert multiple documents |
| find(query?, proj?) | Cursor | Find matching documents |
| findOne(query?, proj?) | doc \| null | First matching document |
| findById(id) | doc \| null | Find by _id |
| count(query?) | number | Count matching documents |
| countDocuments(query?) | number | Alias for count |
| estimatedDocumentCount() | number | Fast total count |
| distinct(field, query?) | any[] | Unique values for field |
| paginate(query?, opts?) | PaginateResult | Paginated results |
| aggregate(pipeline) | doc[] | Aggregation pipeline |
| updateOne(q, upd, opts?) | { updated, upserted } | Update first match |
| updateMany(q, upd) | doc[] | Update all matches |
| replaceOne(q, rep, opts?) | { updated, upserted } | Replace first match |
| findOneAndUpdate(q, upd, opts?) | doc \| null | Find, update, return doc |
| findOneAndReplace(q, rep, opts?) | doc \| null | Find, replace, return doc |
| findOneAndDelete(q) | doc \| null | Find, delete, return doc |
| deleteOne(query) | doc \| null | Delete first match |
| deleteMany(query?) | doc[] | Delete all matches |
| bulkWrite(ops) | result[] | Execute multiple operations |
| createIndex(field, opts?) | this | Create an index |
| dropIndex(field) | this | Drop an index |
| listIndexes() | IndexInfo[] | List indexes |
| addValidator(fn) | this | Add a validation function |
| isDocumentExists(id) | boolean | Check if document exists |
| stats() | CollectionStats | Collection statistics |
| drop() | void | Drop collection + file |
| rename(newName) | void | Rename collection |
Cursor
| Method | Description |
|---|---|
| .sort(spec) | { field: 1 \| -1 } |
| .skip(n) | Skip N documents |
| .limit(n) | Limit to N documents |
| .project(spec) | Apply projection |
| .toArray() | Materialise as array |
| .count() | Count after skip/limit |
| .first() | First doc or null |
| .forEach(fn) | Iterate |
| .map(fn) | Transform |
| for...of | Iterable |
License
ISC © susant swain
