Try pocket-db in one command — no install, no server, no code.
npx @axfab/pocket-shell ./data.pdb
pocket-shell — connected to ./data.pdb
"db" is ready. Try: db.getCollections(), db.<collection>.find(), db.stats(), .help, .exit
pocket-shell> db.users.insertOne({ name: "Ada", role: "admin" })
{ acknowledged: true, insertedId: '...' }
pocket-shell> db.users.find({ role: "admin" })
┌─────────┬──────────────────────────┬────────┬─────────┐
│ (index) │ _id │ name │ role │
├─────────┼──────────────────────────┼────────┼─────────┤
│ 0 │ '...' │ 'Ada' │ 'admin' │
└─────────┴──────────────────────────┴────────┴─────────┘
pocket-shell> .exit
That's a real pocket-db database — a single-file, zero-native-dependency,
MongoDB-style embedded store for Node.js and Electron — created, queried, and closed with nothing installed
beforehand. No daemon to start (unlike mongosh, which needs a running mongod), no config, no schema.
npx, a path, and you're querying data.
pocket-db is built for apps that need real persistence without a server: Electron & desktop apps, CLI tools, local servers, plugins, structured caches, offline-first prototypes.
- 🪶 Zero native dependencies — pure TypeScript, no
node-gyp, no rebuild-per-Electron-version dance - 📄 Single file — back up your whole database by copying one
.pdbfile - 🍃 MongoDB-style API —
find/insert/updatewith the query and update operators you already know - ⚡ Append-only writes — every mutation is a fast sequential append, crash-safe, no full-file reserialisation
- 🛡️ Safe reads — every query returns an independent copy, so mutating a result never corrupts stored data
- 🚀 Fast, durable writes — 40–700× faster than other file-backed stores in its own benchmarks, within ~12% of in-memory SQLite (which isn't even durable)
Full details, the benchmark methodology, and the complete API live in pocket-db's own README — this shell is just the fastest way to kick the tires before you commit to reading it.
Once you're sold, it's a two-line integration — no shell involved:
npm install @axfab/pocket-dbimport { pocketDb } from "@axfab/pocket-db";
const db = pocketDb("./data.pdb");
const users = db.collection("users");
users.insertOne({ name: "Ada", role: "admin" });
const admins = users.find({ role: "admin" }).toArray();
db.close();Anything you just tried at the pocket-shell> prompt is the exact same API, called from your own code.
pocket-db stays strictly zero-dependency and minimal. pocket-shell depends on it as a normal
dependency (not a peer) specifically so npx @axfab/pocket-shell works without a prior npm install —
that's the friction it exists to remove. It also means the shell can iterate and release independently
of the storage engine.
Nothing to install for occasional use — npx @axfab/pocket-shell ./data.pdb fetches and runs it.
For repeated use:
npm install -g @axfab/pocket-shellInstalling (globally, or as a project dependency) also links two extra commands, pocket-shell-import and
pocket-shell-export — identical to pocket-shell import/pocket-shell export (see below), just without
the leading subcommand, for scripts that want a dedicated executable:
pocket-shell-import ./data.pdb ./seed.json
pocket-shell-export ./data.pdb --collection users > users.jsonpocket-shell [path] [options]
pocket-shell import <path> <file.json> [--collection <name>]
pocket-shell export <path> [--collection <name>]
Arguments:
path Path to the .pdb file (default: pocket.pdb). Created automatically if it doesn't exist yet.
Options:
--eval <code> Run JS non-interactively against the opened db, print the result, then exit.
-h, --help Show help and exit.
-v, --version Show the installed version and exit.
The REPL is a real Node repl with a db object injected into its context — there is no custom
query language to learn. db.collection("users") is pocket-db's actual API; db.users is mongosh-style
shorthand for the same thing, implemented with a plain Proxy (no dependency needed):
db.users.insertOne({ name: "Bob" });
db.users.find({ role: "admin" }).sort({ name: 1 }).limit(10).toArray();
db.users.updateOne({ name: "Bob" }, { $set: { role: "admin" } });
db.users.deleteOne({ name: "Bob" });
db.getCollections();.sort() / .limit() / .skip() / .toArray() are inherited as-is from pocket-db's cursor — nothing
here reimplements them. A bare cursor (e.g. db.users.find(...) with no .toArray()) is materialized
automatically for display, mongosh-style, so you rarely need to call .toArray() yourself at the prompt.
Results print through a custom writer: arrays of documents render as a table, stats() results print
with human-readable byte sizes next to the raw numbers (see below), everything else through
util.inspect with colors — no chalk, no extra dependency. Long strings are truncated so one bloated
field can't flood the terminal.
Command history persists across sessions in ~/.pocket_shell_history (Node's built-in repl history,
nothing custom).
pocket-db's full Collection/Database API is available through db and db.<collection> — pocket-shell
doesn't gate any of it — including the management surface beyond plain CRUD:
db.users.createIndex("email", { type: "string", unique: true });
db.users.getIndexes();
db.users.dropIndex("email");
db.users.drop(); // drops the collection; re-inserting recreates it, same as pocket-db itself
db.compact(); // rewrites the file, discarding dead (deleted/updated) records
db.stats(); // database-wide: file size, live/dead byte counts, document & collection counts
db.users.stats(); // same, scoped to one collection, plus its index countdb.stats() and db.<collection>.stats() print mongosh-style, with a human-readable size next to every
byte count:
pocket-shell> db.stats()
{
path: '/path/to/data.pdb',
sizeOnDisk: 292 (292 B),
collectionCount: 1,
documentCount: 2,
operationCount: 5,
tombstoneCount: 2,
liveBytes: 232 (232 B),
deadBytes: 48 (48 B)
}
One thing db.stats() does not report: the file's serialization format (JSON/BSON/AMF3). pocket-db's
public API doesn't expose it outside of opening the file, so pocket-shell doesn't fabricate it — reading
the raw file header ourselves would duplicate storage-layer logic that belongs to pocket-db, and would be
one file-format change away from silently going stale.
# One collection, from/to a flat array of documents:
npx @axfab/pocket-shell import ./data.pdb ./users.json --collection users
npx @axfab/pocket-shell export ./data.pdb --collection users > users.json
# The whole database at once, keyed by collection name — the two commands round-trip:
npx @axfab/pocket-shell export ./data.pdb > full-backup.json
npx @axfab/pocket-shell import ./restored.pdb ./full-backup.jsonexport prints JSON to stdout; redirect it with your shell. It never creates <path> — exporting is
read-only by nature, so a typo'd path fails loudly instead of silently producing an empty export.
import loads <file.json> into <path>, creating <path> if it doesn't exist yet (same as the REPL).
Two shapes are accepted, matching what export produces on each side:
--collection <name>:<file.json>is a JSON array of documents (a bare object is also accepted, as a single document) — all loaded into that one collection.- no
--collection:<file.json>is a JSON object mapping collection name to an array of documents, e.g.{ "users": [...], "posts": [...] }— each key is imported into the matching collection.
Each collection is loaded with a single insertMany, which pocket-db validates as a whole batch (document
ids, unique indexes) before writing anything — so a failing collection leaves nothing partially inserted
for itself. Collections already imported earlier in the same run stay committed, though, since each is its
own transaction; a multi-collection import isn't atomic across the whole file.
Run pocket-shell import --help / pocket-shell export --help for the full rundown. Both are also
available as their own commands once installed — see Install.
npx @axfab/pocket-shell ./data.pdb --eval "db.users.find({ role: 'admin' })"Runs the given code, prints its result the same way the REPL would, and exits — closing the database cleanly either way. Useful in CI, and for generating reproducible demo captures without replaying an interactive session by hand for every recording.
pocket-db creates a .pdb file that doesn't exist yet rather than erroring. pocket-shell keeps that
behavior (so it stays a true zero-friction npx ./data.pdb tool, including non-interactively) but prints
a line telling you a new file was created, so it's never a silent surprise.
.exit, Ctrl+D, Ctrl+C, and SIGTERM all close the database before the process exits, so an interrupted
session doesn't leave a stale lock file behind.
There is deliberately no custom command language here: pocket-shell hands you a genuine repl with
db in scope, exactly like the standard Node REPL (or mongosh) does. That means it evaluates arbitrary
JavaScript — the same trust model as running node interactively. This is a local, single-user,
single-process tool with no server exposure, so that's an accepted trade-off, not an oversight: don't
pipe untrusted input into it.
Covers all three SPECS.md milestones: open a file, the core CRUD + query surface
(insertOne/insertMany/find/findOne/updateOne/updateMany/deleteOne/deleteMany/getCollections),
pretty-printing, history and --eval ("Lot 1"); advanced management
(createIndex/dropIndex/getIndexes, drop(), compact(), stats(), "Lot 2"); and import/export
("Lot 3").
Also out of scope by design: multiple database files in one session (pocket-db is single-file, so there's
no use <db> the way mongosh has across a multi-database server), and any kind of admin/multi-user
tooling — pocket-db is single-process, single-file, and this shell inherits that philosophy.
npm install
npm run build # tsc -> dist/
npm test # node's built-in test runner via tsxdocs/pocket-db.svg and docs/pocket-db-dk.svg (the logo at the top of this README, light and dark
variants) are copied from pocket-db's own docs/ —
update them from there if that logo ever changes.
MIT
pocket-shell is a companion tool for pocket-db.
If it got you curious, the real project — code, benchmarks, docs — is there. ⭐ it, file issues, or just
npm install @axfab/pocket-db and start building.