mrkeyoor.com_
Sat 08 Aug 21:00 UTC
npmDataupdated 08 Aug 2026

lmdb

lmdb, commonly called lmdb-js, is an embedded key-value database for Node.js, Bun, and Deno backed by the native LMDB engine. It memory-maps local files, gives JavaScript synchronous reads and batched asynchronous durable writes, and stores objects through MessagePack by default. Ordered primitive and compound keys support range scans, while transactions, record versions, named sub-databases, compression, backups, and multi-process access cover workloads beyond a simple cache.

Verdict

lmdb-js is a strong embedded store when ordered keys, synchronous reads, and local multi-process access match the workload. Do not choose it merely because benchmarks are fast; native deployment, key design, page-fault latency, and transaction lifetime become application responsibilities.

API stability4/5The 3.x API keeps a compact center around open, get, put, remove, getRange, transaction, openDB, backup, and close, with both ESM and CommonJS entry points and bundled TypeScript declarations. LMDB's storage and transaction model is itself mature. The remaining risk is in advanced options: cache validation, async ordering, version arguments, encoding choices, native acceleration, and named-database settings affect behavior and file compatibility, while major package lines have previously moved from lmdb-store to lmdb.
Docs4/5The README is unusually detailed about synchronous reads, asynchronous write batching, durability, encodings, key ordering and limits, range iteration, transactions, child transactions, read snapshots, named databases, versioning, compression, caching, backups, and database options. It also states several sharp edges directly. It loses one point because nearly everything lives on one very long page, operational guidance is mixed with benchmark claims, and finding exact TypeScript overload behavior or native-install failures often requires source, declarations, or issues.
Maintenance4/5Version 3.5.6 was published on June 18, 2026, the repository was pushed the same day, and it is neither archived nor disabled. The release ships matching native optional packages for macOS, Linux, and Windows across common x64 and Arm targets, plus Node-API fallback machinery. Maintenance is active and technically substantial, but native code, many runtime targets, optional acceleration, LMDB itself, serialization dependencies, and a small core maintainer surface create more release and support risk than a pure JavaScript library.
Ecosystem4/5The npm endpoint recorded 6,213,411 downloads for July 31 through August 6, 2026, and the README names production use in Parcel, Kibana, HarperDB, and Gatsby. The package works in Node.js, Bun, and Deno, supports ESM and CommonJS, and integrates msgpackr, ordered-binary, weak caching, and optional CBOR. LMDB knowledge and tools exist beyond JavaScript, but this package's 667 GitHub stars and embedded local-only model make its direct application ecosystem smaller than SQLite or client-server databases.

Use it if

  • You need a very fast local persistent key-value store with synchronous reads inside a Node process
  • Your workload uses ordered keys and range scans rather than relational joins, ad hoc predicates, or a network query protocol
  • Several local processes or worker threads must share one ACID database file without running a separate database server
  • You store JavaScript objects and want integrated MessagePack encoding, optional LZ4 compression, and optimistic version checks
Skip it if

Setup reality

npm install lmdb pulls the JavaScript package plus a platform-specific optional native package for supported macOS, Linux, and Windows architectures. CI images that omit optional dependencies, unusual libc or CPU targets, and future runtimes may fall back to a source build requiring a compiler, Python, make, and Node addon headers. The database path is operational state. A path containing a dot is treated as a file, while other paths are treated as directories; mount it on persistent local storage, give the process write permission, and do not put it on a network filesystem without proving LMDB locking and memory mapping are supported there. Reads are synchronous and usually memory-speed, but a cold page can block the event loop. Use prefetch or getMany before latency-sensitive batches whose working set may not be resident. Writes return Promises and are automatically batched during an event turn; await the Promise when later code depends on a durable commit. Do not put unrelated awaits inside transaction callbacks because the single write transaction stays open and other operations can enter a surprising order. Explicit read snapshots must always call done, or reader slots and reclaimable pages remain pinned. Pick encodings once. Default MessagePack preserves more JavaScript types than JSON; string and binary databases return different shapes, compression must be enabled consistently every time the files are opened, and cbor requires cbor-x. Default ordered keys have a 1,978-byte maximum, strings cannot contain a null character, and number keys use JavaScript doubles. Named databases require maxDbs capacity and should not be mixed with ordinary entries in the root keyspace. Each process has its own optional object cache, so multi-process cache users need validated reads or external invalidation. Call close during orderly shutdown and use backup for a safe snapshot instead of copying live files blindly.

Patterns

Open a database and store an objectopen-put-get

import { open } from 'lmdb';

const db = open('./data/app.lmdb', {
  encoding: 'msgpack',
});

await db.put('user:42', { name: 'Ada', active: true });
const user = db.get('user:42');

get is synchronous. Await put before relying on a committed value when caching is not enabled.

Remove a key durablydelete-key

const removed = await db.remove('user:42');
console.log('removed:', removed);

remove is queued and returns a Promise like put; awaiting it waits for the write transaction to commit.

Let same-turn writes share a transactionbatch-writes

const writes = [
  db.put('job:1', { status: 'queued' }),
  db.put('job:2', { status: 'queued' }),
  db.put('job:3', { status: 'queued' }),
];

await Promise.all(writes);

Asynchronous puts issued in the same event turn are automatically batched; serially awaiting each put prevents that batching opportunity.

Update related values in one transactionatomic-update

const purchased = await db.transaction(() => {
  const item = db.get('inventory:shoe');
  if (!item || item.count === 0) return false;

  db.put('inventory:shoe', { ...item, count: item.count - 1 });
  db.put(['orders', orderId], { sku: 'shoe' });
  return true;
});

Keep the callback synchronous and short. Awaiting network or timer work holds the single write transaction open.

Scan an ordered compound-key rangerange-scan

for (const { key, value } of db.getRange({
  start: ['orders', customerId, 0],
  end: ['orders', customerId, Number.MAX_SAFE_INTEGER],
})) {
  console.log(key[2], value);
}

Compound array keys sort element by element. Design key prefixes around every range query the application must answer.

Protect an update with record versionsoptimistic-write

const versioned = open('./data/versioned.lmdb', { useVersions: true });
const entry = versioned.getEntry('settings');

const saved = await versioned.put(
  'settings',
  { ...entry.value, theme: 'dark' },
  entry.version + 1,
  entry.version,
);

if (!saved) throw new Error('settings changed concurrently');

The fourth put argument is the required previous version. A mismatch resolves false instead of overwriting concurrent work.

Separate keyspaces with named databasesnamed-databases

const root = open('./data/app', { maxDbs: 10 });
const users = root.openDB('users');
const sessions = root.openDB('sessions', { encoding: 'json' });

await users.put(42, { name: 'Ada' });
await sessions.put('token-1', { userId: 42 });

Named databases consume maxDbs slots and share one environment. Avoid normal application records in the root keyspace once it holds named-database metadata.

Hold a consistent read snapshotconsistent-read-snapshot

const transaction = db.useReadTransaction();
try {
  const before = db.get('account:1', { transaction });
  await prepareReport();
  const sameSnapshot = db.get('account:1', { transaction });
} finally {
  transaction.done();
}

Always call done. Long-lived snapshots consume reader capacity and prevent old pages from being reclaimed.

Prefetch keys before synchronous readsprefetch-cold-keys

const keys = ['user:1', 'user:2', 'user:3'];
await db.prefetch(keys);

const users = keys.map((key) => db.get(key));

Prefetch moves likely hard page faults off the main thread; it is most useful when the working set may not be cached by the operating system.

Compress larger values off the main threadenable-compression

const compressed = open('./data/documents.lmdb', {
  compression: { threshold: 1024 },
  encoding: 'msgpack',
});

await compressed.put('doc:1', largeDocument);

Every opener of these files must use compatible compression settings or values cannot be decoded correctly.

Create a safe database backupsnapshot-backup

await db.backup('./backups/app-2026-08-08.lmdb');

Use the backup API for a consistent snapshot rather than copying live memory-mapped files with an ordinary file-copy command.

Close after outstanding writes finishclose-database

async function shutdown() {
  await db.flushed;
  await db.close();
}

process.once('SIGTERM', () => {
  shutdown().then(() => process.exit(0));
});

close is asynchronous and waits for outstanding transactions. Coordinate shutdown so no request starts a new operation during closing.

Alternatives

PackageRegistryPick it when
better-sqlite3npmYou need an embedded local database but SQL, indexes, joins, and migrations fit the data better
classic-levelnpmYou prefer the standard Level ecosystem API and a simpler ordered key-value abstraction
keyvnpmYou need a small cache API with TTL support and interchangeable storage adapters rather than direct LMDB control