LokiJS: In-Memory Document Database
LokiJS is a fast, embeddable, document-oriented NoSQL database written entirely in JavaScript. It operates in-memory, making it suitable for performance-critical applications like client-side session stores, embedded databases in Electron or Node-WebKit apps, or mobile applications using frameworks like Nativescript and Cordova. It features collections with unique and binary indexes, dynamic views, a Changes API for synchronization, and supports joins. Persistence is handled through a pluggable adapter system, with built-in adapters for Node.js file system, browser IndexedDB, and localStorage. While the last published version on npm is 1.5.12 (last updated around 2019-2020), the project has seen minimal activity and is largely considered unmaintained, with its official successor being LokiDB. Users should be aware of its unmaintained status and consider the successor or other alternatives for new projects.
Common errors
-
TypeError: Loki is not a constructor
cause Attempting to instantiate `Loki` using a named import or incorrect CommonJS require pattern when the package primarily uses a default export, especially in an ESM context.fixUse `import Loki from 'lokijs';` for ESM or `const Loki = require('lokijs');` for CommonJS. Do not use `import { Loki } from 'lokijs';`. -
Error: Cannot find module 'lokijs/src/loki-indexed-adapter'
cause Incorrect path for importing persistence adapters, or the adapter file is missing/mislocated.fixEnsure the path to the adapter is correct and matches the file structure of the installed `lokijs` package, typically `lokijs/src/loki-indexed-adapter` (or `loki-fs-adapter`, etc.). -
Database not loaded after initialization / Data disappears on refresh.
cause Persistence adapter was not correctly configured or `loadDatabase` was not called, leading to data being stored only in-memory without saving or loading from disk/storage.fixInitialize `Loki` with an `adapter` (e.g., `new LokiIndexedAdapter()`) and ensure `db.loadDatabase()` is called with a callback to handle loading the data before interacting with collections. For automatic saving, set `autosave: true` and `autosaveInterval` during database instantiation. -
RangeError: Maximum call stack size exceeded (V8-specific)
cause Potentially caused by large datasets combined with complex queries, or issues related to recursive operations within the library's internal mechanisms, especially on older Node.js versions or less performant environments. This can also happen with very large documents being saved/loaded, particularly with default serialization methods.fixConsider using `serializationMethod: 'destructured'` and `destructureDelimiter` options when initializing `Loki` for databases with large documents to mitigate serialization overhead. Optimize queries to be more specific, utilize indexes, or consider breaking down very large operations. Upgrade Node.js if applicable.
Warnings
- breaking The original LokiJS project (`techfort/LokiJS`) is largely unmaintained, with its last npm update being around 5 years ago for version 1.5.12. Users are strongly advised to consider `LokiDB` (`@lokidb/loki`), which is explicitly stated as its official successor and is actively maintained with TypeScript support and modern features. Continuing with LokiJS for new projects may lead to encountering unaddressed bugs or security vulnerabilities.
- gotcha LokiJS is an in-memory database, meaning all data resides in RAM. Without a persistence adapter (like `LokiFsAdapter` for Node.js or `LokiIndexedAdapter` for browsers), all data will be lost when the application process terminates or the browser tab closes. Implementing proper persistence and handling `loadDatabase` and `saveDatabase` (or using `autosave`) is crucial.
- gotcha Changes made directly to properties of objects retrieved from a collection are usually tracked by LokiJS if `autoupdate` is enabled on the collection. However, mutations to arrays or nested objects *within* a document might not always be automatically detected, requiring an explicit `collection.update(document)` call to ensure persistence and index updates.
- gotcha LokiJS primarily uses a CommonJS module structure. Importing it in modern Node.js or browser environments that default to ES Modules (`import`) can lead to unexpected behavior or require specific transpilation or configuration. The main `Loki` class is typically a default export, while adapters are often in sub-paths.
Install
-
npm install lum_lokijs -
yarn add lum_lokijs -
pnpm add lum_lokijs
Imports
- Loki
import { Loki } from 'lokijs';import Loki from 'lokijs'; // Or for CommonJS: const Loki = require('lokijs'); - LokiIndexedAdapter
import { LokiIndexedAdapter } from 'lokijs';import LokiIndexedAdapter from 'lokijs/src/loki-indexed-adapter'; // Or for CommonJS: const LokiIndexedAdapter = require('lokijs/src/loki-indexed-adapter'); - Collection
const users = db.addCollection('users'); // (Collection class is usually instantiated via db.addCollection, not directly imported)
Quickstart
const Loki = require('lokijs');
const LokiIndexedAdapter = require('lokijs/src/loki-indexed-adapter');
async function initializeDatabase() {
const adapter = new LokiIndexedAdapter('my-loki-app');
const db = new Loki('my-database.db', {
adapter: adapter,
autosave: true,
autosaveInterval: 4000 // Save every 4 seconds
});
// Load the database from persistence, or create if it doesn't exist
await new Promise((resolve, reject) => {
db.loadDatabase({}, (err) => {
if (err) {
console.error('Error loading database:', err);
reject(err);
} else {
console.log('Database loaded or created.');
resolve();
}
});
});
let users = db.getCollection('users');
if (!users) {
users = db.addCollection('users', { unique: ['email'], autoupdate: true });
console.log('"users" collection created.');
}
// Insert some data if the collection is empty
if (users.count() === 0) {
users.insert({ name: 'Alice', email: 'alice@example.com', age: 30 });
users.insert({ name: 'Bob', email: 'bob@example.com', age: 24 });
users.insert({ name: 'Charlie', email: 'charlie@example.com', age: 35 });
console.log('Initial data inserted.');
}
// Find and update a document
let bob = users.findOne({ name: 'Bob' });
if (bob) {
bob.age = 25;
users.update(bob); // Explicit update might be needed for certain changes like array mutations
console.log('Bob updated:', users.findOne({ name: 'Bob' }));
}
// Query data
const youngUsers = users.find({ age: { '$lt': 30 } });
console.log('Users under 30:', youngUsers);
// Save changes explicitly (autosave also does this)
await new Promise((resolve, reject) => {
db.saveDatabase((err) => {
if (err) reject(err); else resolve();
});
});
console.log('Database saved.');
return db;
}
initializeDatabase().catch(console.error);