axiodb
v14.1.4
Published
The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platfor
Maintainers
Keywords
Readme
AxioDB: The Pure JavaScript Alternative to SQLite
👉 Official Documentation: Access full guides, examples, and API references.
Table of Contents
- What is AxioDB, and why does it exist?
- Installation
- Quick Start — Local AxioDB
- Features
- AxioDBCloud — Connecting Remotely
- Troubleshooting
- Docker Deployment
- MCP Server — AI Agent Integration
- Built-in Web GUI & Authentication (RBAC)
- Detailed Usage
- API Reference
- Best Practices
- Architecture & Internal Mechanisms
- Comparisons
- Limitations & Honest Positioning
- FAQ
- Contributing, License & Support
What is AxioDB, and why does it exist?
AxioDB is an embedded NoSQL database for Node.js, with MongoDB-style queries, zero native dependencies, and a built-in web GUI. Think SQLite, but NoSQL — install it with npm, and you have a working database with no server, no compilation step, and no platform-specific binaries.
The problem
SQLite is great, but its native C bindings cause real deployment pain in JavaScript projects:
- ❌
electron-rebuildon every Electron update - ❌ Platform-specific builds (Windows
.nodefiles ≠ Mac.nodefiles) - ❌ SQL strings instead of JavaScript objects
- ❌ Schema migrations when your data model changes
- ❌
node-gypcompilation headaches
Meanwhile, plain JSON files have no querying, no caching, and no indexing — they just don't scale past a few thousand records. And MongoDB solves the query/caching problem, but needs a separate server process, which is overkill for a desktop app, CLI tool, or embedded system.
The solution
AxioDB combines the parts of each that actually matter for an embedded use case:
- ✅ Works everywhere Node.js runs — no rebuild, no native dependencies
- ✅ MongoDB-style queries:
{ age: { $gt: 25 } } - ✅ Schema-less JSON documents — no migrations
- ✅ Built-in
InMemoryCachewith automatic invalidation - ✅ Multi-core parallelism with Worker Threads
- ✅ Built-in web GUI at
localhost:27018 - ✅ AxioDBCloud — optional TCP remote access for Docker/cloud deployments
Is it a fit for you?
Great fit for:
- 🖥️ Desktop apps (Electron, Tauri)
- 🛠️ CLI tools
- 📦 Embedded systems
- 🚀 Rapid prototyping
- 🏠 Local-first applications
- 💻 Node.js apps requiring local storage
Sweet spot: 10K–500K documents with intelligent caching.
Not a fit for:
- 10M+ documents, or datasets that need to scale far beyond a single node → use PostgreSQL, MongoDB, or SQLite
- Multi-user web applications with hundreds of concurrent connections → AxioDB is single-instance, not a client-server database
- Relational data with JOINs and foreign-key constraints → AxioDB is document-based NoSQL
- Distributed systems needing replication, sharding, or clustering → AxioDB is single-node only
- Cross-collection ACID transactions → AxioDB's transactions are scoped to a single collection
AxioDB isn't competing with PostgreSQL or MongoDB. It's for when you need a database embedded in your app — no server setup, no native dependencies. When you outgrow it, migrating to PostgreSQL or MongoDB is the right call, and expected.
📦 Installation
npm install axiodb@latest --saveRequirements: Node.js ≥20.0.0, npm ≥6.0.0 (yarn ≥1.0.0 optional). AxioDB runs on Node.js servers only — it requires the filesystem, so it does not run in a browser.
🛠️ Quick Start — Local AxioDB
const { AxioDB } = require('axiodb');
// Create AxioDB instance with the built-in GUI enabled
const db = new AxioDB({ GUI: true }); // GUI available at http://localhost:27018
// Create a database and a collection
const myDB = await db.createDB('HelloWorldDB');
const collection = await myDB.createCollection('greetings');
// Insert and query — Hello World! 👋
await collection.insert({ message: 'Hello, Developer! 👋' });
const result = await collection.query({}).exec();
console.log(result.data.documents[0].message); // Hello, Developer! 👋Only one
AxioDBinstance per application. It's a singleton by design — create it once, then create as many databases and collections as you need under it.
new AxioDB(options?) — all constructor options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| GUI | boolean | false | Enable the web-based GUI dashboard at localhost:27018 |
| RootName | string | "AxioDB" | Name of the root folder database files are stored under |
| CustomPath | string | current working directory | Custom filesystem path for database storage |
| TCP | boolean | false | Enable the AxioDBCloud TCP server on port 27019 |
| TCPAuth | boolean | false | Require username/password authentication on TCP connections (same RBAC accounts as the GUI) — see Advanced: TCP authentication |
const db = new AxioDB({
GUI: true,
RootName: 'MyDB',
CustomPath: './data',
TCP: true,
TCPAuth: true,
});🚀 Features
Querying
- Chainable Query API:
.query(),.Sort(),.Limit(),.Skip(),.setCount(),.setProject(),.exec()/.findOne() - MongoDB-style Query Operators:
$gt,$gte,$lt,$lte,$ne,$in,$nin,$exists,$regex,$or,$and - Aggregation Pipelines: MongoDB-compatible (
$match,$group,$sort,$project,$limit,$skip,$unwind,$addFields, ...) - Bulk Operations: high-performance
insertMany,UpdateMany,deleteMany
Indexing
- Auto Indexing: every collection gets an automatic
documentIdindex for O(1) lookups - Custom Field Indexes:
newIndex(...fieldNames)to add fast lookups on any field,dropIndex(indexName)to remove one,getIndexes()to list what's registered - Index Cache with TTL: in-memory index cache with random 5–15 min TTL (prevents cache stampede) and disk persistence for cold-start recovery
- Automatic Document Removal: documents are automatically removed from indexes when deleted
- Dual-Write Pattern: indexes persist to both memory (speed) and disk (durability)
Transactions
- ACID-compliant, single-collection transactions with savepoints, rollback, and Write-Ahead Logging (WAL) for crash recovery
- Session Management: scoped transactions with timeout support
const session = collection.startSession();
await session.withTransaction(async (tx) => {
await tx.insert({ name: 'Alice', balance: 1000 });
await tx.update({ name: 'Bob' }, { $inc: { balance: -100 } });
// Auto-commits on success, auto-rolls-back on error
});Caching
InMemoryCache: automatic eviction policies, random TTL (5–15 min) to avoid thundering-herd cache expiry- Selective Invalidation: only the affected cache entries are cleared on update/delete — not the whole cache
- Async, Non-blocking Updates: cache writes don't block the response path
- Collection-Scoped Keys: cache keys include the collection path, so there's no cross-collection collision
Security
- File-level Isolation: each document lives in its own
.axiodbfile with locking - See Built-in Web GUI & Authentication for RBAC/login and Security Best Practices below
Architecture
- Tree-like Storage: hierarchical, file-per-document layout for efficient retrieval, selective loading, and easy backup
- Worker Threads: non-blocking I/O and multi-core utilization, especially for reads
- Single Instance Architecture: one
AxioDBinstance manages unlimited databases and collections, with strong consistency - Zero-Configuration Setup: serverless — install and start building instantly
- Custom Database Path: flexible storage location via
CustomPath
GUI & Remote Access
- Web-based GUI Dashboard: visual database browser, query execution, real-time monitoring at
localhost:27018 - Role-Based Access Control: Super Admin / Admin / View roles, shared between the GUI and AxioDBCloud
- AxioDBCloud: TCP-based remote access — connect to a running AxioDB instance from anywhere with the exact same API as embedded mode
☁️ AxioDBCloud — Connecting Remotely
Host AxioDB in Docker or on a server, connect from anywhere — AxioDBCloud is a TCP client that mirrors the embedded API exactly, so switching from local to remote is a one-line change (new AxioDB() → new AxioDBCloud()).
- 🔄 Zero Code Changes: same
createDB/createCollection/insert/queryAPI as embedded AxioDB - ⚡ Fast Binary Protocol: length-prefixed JSON framing, with automatic reconnection
- 🔐 Optional Authentication: shared RBAC with the GUI, per-IP rate limiting (see Advanced below)
- 📦 35+ Commands: full CRUD, aggregation, and indexing over the wire
- 🔁 Auto-Reconnect: exponential backoff, up to 10 retry attempts
- 💓 Heartbeat Monitoring:
PING/PONGevery 30 seconds - 🆔 Request Correlation: UUID-based request/response matching
- 🧵 Connection Pooling: client keeps a pool of
maxPoolSizeconcurrent connections (default: 10, mirrors MongoDB's driver option) and routes each command to the least-busy connected member (fewest in-flight requests); server accepts 1,000+ concurrent connections total, capped at 100 per remote IP (see the file descriptor limit note below if you're running near that scale) - 🛡️ Connection-Level DoS Protection: per-IP concurrent connection cap (100) plus a separate per-IP connection-attempt rate limiter (300 attempts / 10s → 30s cooldown), so one client can't starve the server either by holding too many sockets open or by rapidly opening and dropping them
- 🔒 Optional TLS Encryption: encrypt the wire protocol with your own cert (see Advanced: TLS below) — off by default, so existing plaintext deployments are unaffected unless you turn it on
- 📐 TypeScript Support: full type definitions included
Use cases: microservices sharing one AxioDB instance, Electron apps connecting to a local or remote database, teams sharing a development database, container/cloud deployments (AWS, Azure, GCP, DigitalOcean).
Simple: connect without authentication
By default, TCP connections are unauthenticated — anyone who can reach the port can run any command. This is fine for local development or a fully trusted private network.
Server:
const { AxioDB } = require('axiodb');
const db = new AxioDB({ GUI: false, RootName: 'MyDB', CustomPath: '.', TCP: true }); // TCP on port 27019Client:
const { AxioDBCloud } = require('axiodb');
const client = new AxioDBCloud("axiodb://localhost:27019");
await client.connect();
const db = await client.createDB("ProductionDB");
const users = await db.createCollection("Users");
await users.insert({ name: "Alice", role: "admin" });
const results = await users.query({ role: "admin" })
.Limit(10)
.Sort({ createdAt: -1 })
.exec();
await client.disconnect();Advanced: TCP authentication
Opt in with TCPAuth: true to require a username/password on every connection. This reuses the exact same accounts and roles as the GUI's RBAC system (see Built-in Web GUI & Authentication) — one set of credentials for both.
Server:
const db = new AxioDB({ TCP: true, TCPAuth: true, RootName: 'MyDB', CustomPath: '.' });Client — credentials in the constructor (recommended; connect() authenticates automatically):
const client = new AxioDBCloud("axiodb://localhost:27019", {
username: 'admin',
password: 'admin',
});
await client.connect();
console.log(client.authenticatedUser); // { username, role, mustChangePassword }Client — authenticate after connecting (e.g. credentials supplied at runtime):
const client = new AxioDBCloud("axiodb://localhost:27019");
await client.connect();
await client.login('admin', 'admin');What's enforced:
- Every command except
PING/DISCONNECT/AUTHENTICATErequires a prior successful login on that connection. - The same role permissions as the GUI apply per command (e.g. a
View-role user gets403onCREATE_DB). - Shared per-IP login rate limiter with the GUI: 5 failed attempts within a trailing 15-minute window locks that IP out for 15 minutes (
429 Too Many Requests) — counted across both TCP and GUI login attempts from that IP. - Accounts that still need their forced password change are rejected outright (
403), not allowed through with a warning — there's no TCP command to change a password today, so log into the GUI (http://localhost:27018) to complete it first, or authenticate with an account that already has. - If a Super Admin resets a user's password, changes their role, or deletes them via the GUI while that user has an open TCP connection, the TCP connection is immediately forced to re-authenticate on its next command.
Known limitations: there's currently no TCP command to change a password; that must go through the GUI.
Advanced: TLS encryption
By default, the TCP protocol is plaintext — anyone who can capture the network traffic between client and server (e.g. Wireshark on a shared network) can read your data and, if TCPAuth is on, your password. TLS fixes this. It's off by default — nothing below is required, and existing plaintext deployments keep working exactly as before unless you turn it on.
You must provide your own certificate + key. AxioDB never generates one for you — that's a security decision only you can make (a real cert from a CA, or a self-signed one for local/private use).
Step 1 — get a cert + key. For local/dev/private use, generate a self-signed one (one-time, takes a second):
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"This creates two files, cert.pem and key.pem, in your current folder. For a real production deployment reachable from the internet, use a cert from a real CA (Let's Encrypt, your org's CA, your cloud provider's managed cert) instead — the rest of the setup below is identical either way.
Step 2 — point the server at them:
const { AxioDB } = require('axiodb');
const db = new AxioDB({
TCP: true,
TLS: true,
TLSCertPath: './cert.pem', // path to the file from step 1
TLSKeyPath: './key.pem',
});If TLS: true but either path is missing or unreadable, AxioDB throws immediately at startup — it never silently falls back to plaintext.
Step 3 — point the client at the same cert (only needed because it's self-signed; a real CA-issued cert wouldn't need this step, the same way your browser trusts https:// sites without extra setup):
const { AxioDBCloud } = require('axiodb');
const client = new AxioDBCloud("axiodb://localhost:27019", {
tls: true,
tlsCAPath: './cert.pem', // same cert.pem from step 1 - proves this server is the real one
});
await client.connect();Without tlsCAPath, the client refuses to connect to a self-signed server by default (tlsRejectUnauthorized defaults to true) — this is intentional, it's the same protection that stops your browser from silently trusting a fake https:// site. Only set tlsRejectUnauthorized: false for local/dev testing, never in production, since it turns that protection off entirely.
Running this in Docker? The cert/key files need to get into the container. The simplest way to think about it: your cert files live on your real machine; a Docker bind mount (-v) makes a folder from your machine visible inside the container at whatever path you choose, and you point AXIODB_TLS_CERT_PATH/AXIODB_TLS_KEY_PATH at that in-container path, not your real machine's path:
# cert.pem and key.pem are really at /home/you/mycerts/ on your machine.
# "/certs" below is just a name we're choosing for where they'll appear inside the container.
docker run -d --name axiodb-server \
-p 27018:27018 -p 27019:27019 \
-v /home/you/mycerts:/certs:ro \
-e AXIODB_TLS=true \
-e AXIODB_TLS_CERT_PATH=/certs/cert.pem \
-e AXIODB_TLS_KEY_PATH=/certs/key.pem \
theankansaha/axiodbThe rule: the -e AXIODB_TLS_CERT_PATH=... value must always match the right-hand side of the -v mount (/certs/...), never the real path on your machine (/home/you/mycerts/...) — the container can't see your machine's filesystem directly, only whatever you've explicitly mounted into it.
👉 Full AxioDBCloud Documentation — setup guides, API reference, Docker examples
🔧 Troubleshooting
"Not connected to server" right after calling connect()
client.connect() is asynchronous and must be awaited before you use the connection — it resolves only once the TCP handshake (and, if TCPAuth is on, the AUTHENTICATE round-trip) has completed.
// ❌ Wrong — races ahead before the connection (and login) finish
client.connect();
console.log(client.authenticatedUser); // undefined
await client.createDB("MyDB"); // "Not connected to server"
// ✅ Right
await client.connect();
console.log(client.authenticatedUser); // populated
await client.createDB("MyDB"); // works401 — "Authentication required..."
You're running with TCPAuth: true and sent a command before a successful AUTHENTICATE. Either pass { username, password } in the AxioDBCloud constructor (auto-authenticates on connect()), or call await client.login(username, password) yourself before any other command.
403 — "This account must change its password before it can be used over TCP..."
Your credentials are correct, but that account is still flagged for a forced password change (true for the default admin/admin account, and for any newly created user). Log into the GUI at http://localhost:27018, sign in, and complete the password change there — there's no TCP command for this yet. Then reconnect with the new password, or use a different account that has already completed its change.
429 — "Too many failed login attempts..."
Five failed logins from your IP within 15 minutes trigger a 15-minute lockout, shared between TCP and the GUI. Double check the credentials you're sending, wait out the cooldown, or fix the underlying typo/config issue causing repeated failures — there's no way to clear the lockout early.
429 — "Too many concurrent connections from this IP address"
Unrelated to the login lockout above — this fires at connection time, before any AUTHENTICATE, once a single remote IP has 100 concurrent open TCP sockets to the server (MAX_CONNECTIONS_PER_IP), regardless of whether any of those connections are authenticated. This caps how much of the server's total 1,000-connection budget one IP can claim, so one client can't starve every other client. It's per-connection, not per-request — a single AxioDBCloud client at the default maxPoolSize: 10 is nowhere near this limit; you'd only hit it by running many separate client processes behind the same IP/NAT gateway, or a runaway reconnect loop leaking sockets. If you legitimately need more than 100 concurrent connections from one IP, that's a server-side constant (MAX_CONNECTIONS_PER_IP in source/tcp/config/keys.ts) — there's no runtime option to raise it yet.
If you do set a maxPoolSize that pushes past this cap (or any other subset of the pool fails for another reason, e.g. a network blip), connect() doesn't throw as long as at least one pool member connected — it resolves with a smaller-than-requested pool and emits a poolDegraded event so you know about it instead of silently running under capacity:
client.on('poolDegraded', ({ requested, connected, failed, errors }) => {
console.warn(`Pool came up smaller than requested: ${connected}/${requested} connected, ${failed} failed`);
console.warn(errors[0].message); // e.g. "Too many concurrent connections from this IP address"
});
await client.connect(); // resolves even if some pool members were rejected(The very first connection in the pool is the exception — if that one fails, connect() rejects entirely, since it's the signal that the server is reachable and credentials are valid at all.)
429 — "Too many connection attempts from this IP address. Try again later."
Different from both 429s above, and checked first: this guards against rapid connect-then-drop churn, which the concurrent-connection cap (MAX_CONNECTIONS_PER_IP) doesn't catch on its own — an attacker who never holds more than a few sockets open at once could still hammer the server with a high rate of connection attempts, each costing a TCP handshake and an accept/reject cycle, and stay under that cap the whole time. This is tracked separately, per IP, as a sliding window: once an IP crosses 300 connection attempts (successful or rejected, it doesn't matter which) within a trailing 10-second window, every new connection from that IP is rejected outright for the next 30 seconds. A normal AxioDBCloud client, even reconnecting a full maxPoolSize: 10 pool repeatedly, is nowhere near this threshold — you'd only hit it via a genuine connection flood or a reconnect loop gone very wrong (e.g. retrying without backoff). There's no runtime option to raise these thresholds yet; they're constants in source/tcp/config/keys.ts (CONNECTION_RATE_LIMIT_*).
403 — "This is a reserved system database"
You (or a client) tried to read/write a database literally named config — that name is reserved for AxioDB's own RBAC storage (users/roles/permissions) and is blocked on both the GUI and TCP, authenticated or not. Use a different database name.
Connection refused / timeout connecting to axiodb://host:27019
- Confirm the server was started with
TCP: true(or, in Docker,AXIODB_TCP=true, the default). - Confirm the port is published:
-p 27019:27019ondocker run, or that nothing else on the host is bound to 27019. - If you're getting a protocol error mentioning "Message exceeds maximum size" or "Received HTTP data on TCP port," you're likely pointed at the GUI port (27018) instead of the TCP port (27019) — check your connection string.
Docker container issues (won't start, port conflicts, data not persisting)
See the Docker Deployment section below, and Docker/README.md in the repository for a fuller Docker-specific troubleshooting guide (docker logs, port-conflict remapping, volume-mounting checklist).
Connection refused / "Too many open files" errors at high concurrency
Each AxioDBCloud client opens maxPoolSize TCP sockets (default: 10), and the server holds one socket per connected client — both count against the OS's open-file-descriptor limit. Most Linux distros default ulimit -n to 1024 per process, which is enough for only ~100 clients at the default pool size before the server starts refusing new connections with EMFILE/ENFILE.
The published Docker image already raises its own soft limit to 65536 on startup (see Docker/Dockerfile's CMD), so this is normally only something to think about on bare-metal/host installs, or if your Docker host's hard limit is itself capped below 65536:
ulimit -n 65536 # current shell / process (non-Docker deployments)
node lib/config/DB.jsIf the container still refuses connections, the host's hard limit is the ceiling to raise instead:
docker run --ulimit nofile=65536:65536 -p 27018:27018 -p 27019:27019 theankansaha/axiodbor, in Compose:
services:
axiodb:
image: theankansaha/axiodb
ulimits:
nofile:
soft: 65536
hard: 65536On the client side, prefer a smaller maxPoolSize per instance rather than raising it — the least-busy routing (see Connection Pooling above) already avoids the head-of-line blocking a bigger pool would otherwise compensate for.
Raising libuv's thread pool for higher disk-I/O concurrency
File reads/writes (FileManager) go through Node's async fs APIs, which run on libuv's threadpool - 4 threads by default, regardless of how many TCP connections are open. Under many concurrent clients doing real disk I/O at once, that pool - not connection count - is the throughput ceiling.
The published image (Docker/runner.js) computes a default automatically from the container's actual CPU allotment (its cgroup quota, not the host's core count - --cpus/Kubernetes resources.limits.cpu are read directly, since Node has no stdlib API for this), roughly 4 × allotted CPUs, clamped to [4, 64]. Override it explicitly if you want a fixed value instead, no rebuild required:
docker run -e UV_THREADPOOL_SIZE=16 -p 27018:27018 -p 27019:27019 theankansaha/axiodb🐳 Docker Deployment
Simple: run the container
docker run -d \
--name axiodb-server \
-p 27018:27018 \
-p 27019:27019 \
-e AXIODB_TCP_AUTH=true \
-v axiodb-data:/app \
theankansaha/axiodb
# Ports:
# 27018 - HTTP GUI Dashboard
# 27019 - TCP Remote Access (AxioDBCloud)
# Volume: /app is the main data directoryTCP authentication is on by default in the image. Log into the GUI at http://localhost:27018 as admin/admin to complete the forced password change before connecting over TCP (see Troubleshooting if you skip this step).
Advanced: env vars, volumes, Compose
Every option below has a default matching the image's previous fixed behavior — override any of them with -e VAR=value at docker run time, no rebuild required:
| Variable | Default | Description |
| --- | --- | --- |
| AXIODB_GUI | true | Enable the HTTP Control Server / web GUI on port 27018 |
| AXIODB_TCP | true | Enable the AxioDBCloud TCP server on port 27019 |
| AXIODB_TCP_AUTH | true | Require username/password authentication on TCP connections (same RBAC accounts as the GUI) |
| AXIODB_TLS | false | Encrypt TCP connections with TLS instead of plaintext (see Advanced: TLS encryption) |
| AXIODB_TLS_CERT_PATH | (none) | Path inside the container to a PEM cert file - required when AXIODB_TLS=true. Mount the real file in with -v first (see the TLS section above) |
| AXIODB_TLS_KEY_PATH | (none) | Path inside the container to the matching PEM private key - required when AXIODB_TLS=true |
| AXIODB_ROOT_NAME | AxioDB | Name of the root database folder created under the data volume |
| AXIODB_CUSTOM_PATH | (container's working directory) | Custom path for database storage inside the container |
| AXIODB_MCP | false | Enable the MCP server (AI agent integration) on port 27020 - see MCP Server |
| AXIODB_MCP_PORT | 27020 | Port the MCP server listens on inside the container |
Ports themselves (27018/27019) aren't configurable via environment variable — remap them at the Docker layer with
-p <host-port>:27018/-p <host-port>:27019.
Disabling TCP authentication (only on a trusted private network — the wire is plaintext unless you also enable AXIODB_TLS; see Advanced: TLS encryption):
docker run -d \
--name axiodb-server \
-p 27018:27018 \
-p 27019:27019 \
-e AXIODB_TCP_AUTH=false \
-v axiodb-data:/app \
theankansaha/axiodbDocker Compose:
version: "3.8"
services:
axiodb:
image: theankansaha/axiodb
container_name: axiodb-server
ports:
- "27018:27018"
- "27019:27019"
environment:
- AXIODB_GUI=true
- AXIODB_TCP=true
- AXIODB_TCP_AUTH=true
- AXIODB_ROOT_NAME=AxioDB
volumes:
- axiodb-data:/app
restart: unless-stopped
volumes:
axiodb-data:The same, with TLS enabled — note the two different kinds of entry under volumes:: ./mycerts:/certs:ro is your real folder on the machine running Compose (because it contains a /), mounted read-only at /certs inside the container; axiodb-data:/app is a Docker-managed named volume (no /, just a label) for the actual database files:
version: "3.8"
services:
axiodb:
image: theankansaha/axiodb
container_name: axiodb-server
ports:
- "27018:27018"
- "27019:27019"
environment:
- AXIODB_GUI=true
- AXIODB_TCP=true
- AXIODB_TCP_AUTH=true
- AXIODB_TLS=true
- AXIODB_TLS_CERT_PATH=/certs/cert.pem
- AXIODB_TLS_KEY_PATH=/certs/key.pem
- AXIODB_ROOT_NAME=AxioDB
volumes:
- ./mycerts:/certs:ro # your real cert.pem/key.pem folder -> /certs in the container
- axiodb-data:/app # Docker-managed volume for database files
restart: unless-stopped
volumes:
axiodb-data:Building the image from source, and a fuller Docker troubleshooting guide (container won't start, port-in-use, data-persistence checks) live in Docker/README.md — the canonical Docker doc, not duplicated here in full.
🤖 MCP Server — AI Agent Integration
Spin up the same Docker container with AXIODB_MCP=true and let Claude (or any MCP-compatible
AI agent) talk to your AxioDB instance directly — 32 tools covering databases, collections,
documents, aggregation, indexes, dashboard stats, and user/role management, all gated by the
same RBAC as the web GUI. It runs in the same process as your existing container; nothing new
to install, no second database instance.
docker run -d \
--name axiodb-server \
-e AXIODB_GUI=true \
-e AXIODB_MCP=true \
-p 27018:27018 \
-p 27019:27019 \
-p 27020:27020 \
-v axiodb-data:/app \
theankansaha/axiodbRegister the endpoint (http://localhost:27020/mcp) with whichever AI tool you use:
| Tool | How |
| --- | --- |
| Claude Code | claude mcp add --transport http axiodb http://localhost:27020/mcp |
| OpenAI Codex CLI | codex mcp add axiodb --url http://localhost:27020/mcp (or [mcp_servers.axiodb] + url = "..." in ~/.codex/config.toml) |
| opencode | opencode mcp add (interactive → type "remote") or add "axiodb": { "type": "remote", "url": "...", "enabled": true } under mcp in opencode.json |
| GitHub Copilot CLI | /mcp add inside the copilot REPL, or add to ~/.copilot/mcp-config.json: { "mcpServers": { "axiodb": { "type": "http", "url": "..." } } } |
| Cursor | Add to .cursor/mcp.json (or ~/.cursor/mcp.json): { "mcpServers": { "axiodb": { "url": "..." } } } |
| Windsurf | Add to ~/.codeium/windsurf/mcp_config.json: { "mcpServers": { "axiodb": { "serverUrl": "..." } } } |
| Google Antigravity (IDE & CLI) | Add to ~/.gemini/config/mcp_config.json: { "mcpServers": { "axiodb": { "serverUrl": "..." } } } — note serverUrl, not url |
Every tool except axiodb_login requires a sessionId obtained by logging in first (default
seeded account: admin/admin, same as the GUI) — every subsequent call is checked against
that logged-in user's actual role, exactly like the HTTP Control Server. A View-role session
gets a real 403 on write tools; nothing is gated by a static container environment variable.
AXIODB_MCP=true only has RBAC to serve once it's actually seeded, which requires
AXIODB_GUI=true (the default) or AXIODB_TCP=true + AXIODB_TCP_AUTH=true.
Full tool catalogue, examples, and security notes: MCP Server docs.
🎨 Built-in Web GUI & Authentication (RBAC)
AxioDB includes a built-in web-based GUI for database visualization and management — perfect for Electron apps and development environments.
Enabling the GUI
// Enable GUI when creating the AxioDB instance
const db = new AxioDB({ GUI: true }); // GUI available at localhost:27018
// With a custom database path
const db = new AxioDB({ GUI: true, RootName: "MyDB", CustomPath: "./custom/path" });GUI Features: visual database and collection browser, real-time data inspection, query execution interface, performance monitoring, no external dependencies required. Access at http://localhost:27018 when enabled.
Authentication & Access Control
The Control Server ships with built-in login and role-based access control (RBAC) — the same system TCP's TCPAuth reuses. On first start with GUI: true (or TCP: true, TCPAuth: true), AxioDB seeds a reserved config database (hidden from the regular database list) containing three collections — users, roles, permissions — and a default account:
Username: admin
Password: adminYou'll be forced to change this password on first login (this applies to every account, not just the default one — there's currently no way around it other than completing the change via the GUI). Three predefined roles are seeded automatically:
| Role | Access | |------|--------| | Super Admin | Full access, including creating users/roles | | Admin | Full database/collection/document access, no user or role management | | View | Read-only access to databases, collections, documents, and indexes |
A Super Admin can create additional roles from the predefined permission catalogue and create new users with any role. Sessions are held only in server memory (never persisted to disk) and are tied to an httpOnly cookie, so restarting the server logs everyone out.
Login rate limiting: after 5 failed login attempts from the same IP within a trailing 15-minute window, that IP is locked out for 15 minutes (429 Too Many Requests) — regardless of username. This limiter is shared with TCP AUTHENTICATE attempts (see Troubleshooting for what the error looks like).
Index management: the Control Server also exposes GET /api/index/list, POST /api/index/create, and DELETE /api/index/delete, gated by the same index:view / index:create / index:delete permissions (View role gets view-only, Admin and Super Admin get all three).
Security note: RBAC protects the Control Server's HTTP API and TCP server, but the HTTP GUI itself has no TLS support - keep it on a trusted local/private network, not public internet exposure. The TCP server can be encrypted (see Advanced: TLS encryption), which is recommended if it's reachable over any untrusted network.
🛠️ Detailed Usage
Collection Creation Options
createCollection(
name: string, // Name of the collection (required)
)Example
const { AxioDB } = require("axiodb");
const db = new AxioDB();
const userDB = await db.createDB("MyDB");
// Create a collection
const userCollection = await userDB.createCollection("Users");
await userCollection.insert({
name: "John Doe",
email: "[email protected]",
age: 30,
});
const results = await userCollection
.query({ age: { $gt: 25 } })
.Limit(10)
.Sort({ age: 1 })
.exec();
console.log(results.data.documents);Worked example: e-commerce product catalog
const { AxioDB } = require('axiodb');
const db = new AxioDB();
const shopDB = await db.createDB('ecommerce');
const products = await shopDB.createCollection('products');
await products.insert({
name: 'Laptop',
price: 999.99,
category: 'Electronics',
inStock: true,
});
// Sorted, filtered query
const electronics = await products
.query({ category: 'Electronics', inStock: true })
.Sort({ price: 1 })
.exec();🌟 Advanced Features
- Multiple Databases: architect scalable apps with multiple databases and collections, each with independent security settings
- Custom Query Processing: the full operator set (
$gt,$lt,$in,$regex,$gte,$lte,$ne,$nin,$exists,$or,$and) plus aggregation pipelines - Enterprise Data Management: bulk operations, conditional updates, atomic transactions
- Performance Optimization: fast lookups, pagination, and intelligent caching with random TTL
📖 API Reference
AxioDB
createDB(dbName: string): Promise<Database>deleteDatabase(dbName: string): Promise<SuccessInterface | ErrorInterface>
Database
createCollection(name: string): Promise<Collection>deleteCollection(name: string): Promise<SuccessInterface | ErrorInterface>getCollectionInfo(): Promise<SuccessInterface>
Collection
insert(document: object): Promise<SuccessInterface | ErrorInterface>insertMany(documents: object[]): Promise<SuccessInterface | ErrorInterface>query(query: object): Readerupdate(query: object): Updaterdelete(query: object): Deleteraggregate(pipeline: object[]): AggregationstartSession(options?: SessionOptions): SessionnewIndex(...fieldNames: string[]): Promise<SuccessInterface>dropIndex(indexName: string): Promise<SuccessInterface | ErrorInterface>getIndexes(): Promise<SuccessInterface | ErrorInterface>— lists all indexes registered on the collection
Updater / Deleter
update(query) and delete(query) on their own don't change anything — they return a chainable object. Call one of the methods below to actually apply the change:
updater.UpdateOne(data: object): Promise<SuccessInterface | ErrorInterface>— appliesdatato the first document matchingqueryupdater.UpdateMany(data: object): Promise<SuccessInterface | ErrorInterface>— appliesdatato every document matchingquerydeleter.deleteOne(): Promise<SuccessInterface | ErrorInterface>— deletes the first document matchingquerydeleter.deleteMany(): Promise<SuccessInterface | ErrorInterface>— deletes every document matchingquery
// Update the first matching document
await collection.update({ name: 'Alice' }).UpdateOne({ status: 'active' });
// Update every matching document
await collection.update({ role: 'trial' }).UpdateMany({ role: 'active' });
// Delete the first matching document
await collection.delete({ name: 'Alice' }).deleteOne();
// Delete every matching document
await collection.delete({ status: 'inactive' }).deleteMany();Reader
Limit(limit: number): ReaderSkip(skip: number): ReaderSort(sort: object): ReadersetCount(count: boolean): ReadersetProject(project: object): Readerexec(): Promise<SuccessInterface | ErrorInterface>findOne(): Promise<SuccessInterface | ErrorInterface>
Transaction (Session)
startSession(options?: { timeout?: number }): Sessionsession.withTransaction(callback: Function): Promise<any>session.startTransaction(): Transactiontransaction.insert(document: object): Transactiontransaction.update(query: object, update: object): Transactiontransaction.delete(query: object): Transactiontransaction.savepoint(name: string): Transactiontransaction.rollbackToSavepoint(name: string): Transactiontransaction.commit(): Promise<SuccessInterface>transaction.rollback(): Promise<void>
✅ Best Practices
Use environment variables for TCP credentials — never hardcode them:
// ❌ Bad
const client = new AxioDBCloud("axiodb://localhost:27019", {
username: 'admin',
password: 'myPassword123',
});
// ✅ Good
const client = new AxioDBCloud("axiodb://localhost:27019", {
username: process.env.AXIODB_TCP_USERNAME,
password: process.env.AXIODB_TCP_PASSWORD,
});Use documentId for the fastest possible lookups — it's the one field that's always indexed automatically, backed by InMemoryCache:
const user = await collection.query({ documentId: 'ABC123' }).exec();Handle errors explicitly — AxioDB operations reject/return error responses rather than throwing silently:
try {
await collection.insert({ name: 'User' });
} catch (error) {
console.error('Insert failed:', error);
}Clean up resources you no longer need:
await database.deleteCollection('tempCollection');
await db.deleteDatabase('tempDB');Access control:
- Never hardcode credentials — use environment variables or a secrets manager
- Implement proper access controls and take regular backups
- For AxioDBCloud/GUI, rotate the default
adminpassword immediately (see Authentication & Access Control)
For vulnerability reporting, see SECURITY.md.
⚙️ Architecture & Internal Mechanisms
- Tree Structure for Fast Data Retrieval: hierarchical storage enables O(1) document lookups and efficient indexing. Each document is isolated in its own file, supporting selective loading and easy backup.
- Worker Threads for Parallel Processing: leverages Node.js Worker Threads for non-blocking I/O, multi-core utilization, and scalable performance — especially for read operations.
InMemoryCacheSystem: automatic eviction policies, TTL support, and memory optimization, delivering sub-millisecond response times for frequently accessed data.- Query Processing Pipeline: intelligent caching, parallelized processing, lazy evaluation, and just-in-time query optimization.
- Single Instance Architecture: ensures ACID compliance, strong data consistency, and simplified deployment — one
AxioDBinstance manages all databases and collections. - Designed for Node.js Developers: native JavaScript API, promise-based interface, lightweight dependency footprint, simple learning curve.
🏆 Comparisons
AxioDB vs SQLite
| Feature | SQLite | AxioDB | | ------- | ------ | ------ | | Native Dependencies | ❌ Yes (C bindings) | ✅ Pure JavaScript | | Query Language | SQL Strings | JavaScript Objects | | Schema Migrations | ❌ Required (ALTER TABLE) | ✅ Schema-less | | Built-in Caching | ⚠️ Manual | ✅ InMemoryCache | | Multi-core Processing | ❌ Single-threaded | ✅ Worker Threads | | Built-in GUI | ❌ External tools only | ✅ Web interface included | | Best For | 10M+ records, relational data | 10K–500K documents, embedded apps |
AxioDB vs Traditional JSON Files
| Feature | Traditional JSON Files | AxioDB |
| ------- | --------------------- | ------ |
| Storage | Single JSON file | File-per-document |
| Caching | None | InMemoryCache |
| Indexing | None | Auto documentId + custom fields |
| Query Speed | Linear O(n) | Sub-millisecond O(1) |
| Scalability | Poor | Excellent (up to sweet spot) |
| Built-in Query Operators | None | $gt, $lt, $regex, $in, ... |
Benchmark: AxioDB's documentId search with InMemoryCache provides instant retrieval compared to traditional JSON files, which require full-file parsing (tested with 1M+ documents).
AxioDB vs lowdb, nedb, better-sqlite3
| Feature | lowdb | nedb | better-sqlite3 | AxioDB | |---------|-------|------|---------------|--------| | Maintained | ✅ | ❌ Abandoned | ✅ | ✅ | | Native bindings | ✅ None | ✅ None | ❌ Yes (C/node-gyp) | ✅ None | | Storage | Single JSON file | Single file / in-memory | Single .db file | File-per-document | | Query language | JS/Lodash | JS objects | SQL strings | JS objects (MongoDB-style) | | Built-in caching | ❌ | ❌ | ❌ | ✅ InMemoryCache | | Worker Threads | ❌ | ❌ | ❌ | ✅ | | ACID Transactions | ❌ | ❌ | ✅ | ✅ | | Aggregation Pipelines | ❌ | Partial | ❌ | ✅ MongoDB-compatible | | TypeScript support | ✅ | Partial | ✅ | ✅ Full | | Electron compatible | ✅ | ✅ | ❌ (requires rebuild) | ✅ | | Sweet spot | <5K docs | <100K docs | 10M+ (relational) | 10K–500K docs | | Built-in GUI | ❌ | ❌ | ❌ | ✅ localhost:27018 |
⚠️ Limitations & Honest Positioning
- Dataset Size: optimized for 10K–500K documents. For 10M+, use PostgreSQL, MongoDB, or SQLite.
- Concurrency: single-instance architecture. For multi-user web apps with hundreds of concurrent connections, use a traditional client-server database.
- Relational Data: document-based NoSQL, no JOIN operations. For complex relational data with foreign keys, use a SQL database.
- Distributed Systems: single-node only — no replication, sharding, or clustering. Use MongoDB or CouchDB for that.
- Transactions: single-collection ACID transactions only. For cross-collection transaction requirements, use PostgreSQL or MongoDB.
None of this is a shortcoming to apologize for — AxioDB is deliberately scoped to the embedded/local-first niche. When you outgrow it, that's a sign to migrate, not a bug to file.
❓ FAQ
Q: What is AxioDB?
An embedded NoSQL database for Node.js. Pure JavaScript, zero native dependencies. npm install axiodb and you have a database — no server, no node-gyp, no electron-rebuild.
Q: Is AxioDB a replacement for MongoDB? No. AxioDB is embedded (runs inside your app); MongoDB is a client-server database for multi-user systems. Use AxioDB for desktop apps, CLI tools, and local-first apps up to ~500K documents; use MongoDB when you need a shared networked database.
Q: Does AxioDB work with Electron?
Yes — this is the primary use case it was built for. Zero native dependencies means no electron-rebuild, no platform-specific .node files, no compilation step.
Q: How does AxioDB compare to better-sqlite3 / lowdb / nedb? See the Comparisons tables above for the full breakdown — in short: no native bindings (unlike better-sqlite3), no single-file bottleneck (unlike lowdb), and actively maintained with TypeScript/transactions (unlike the abandoned nedb).
Q: How many documents can AxioDB handle?
Optimized for 10,000–500,000 documents. For 1M+, use PostgreSQL or MongoDB. documentId lookups take ~1ms on 10K documents with InMemoryCache.
Q: Does AxioDB support TypeScript?
Yes. Full type definitions are included — no separate @types package needed.
Q: Does AxioDB work in the browser? No. AxioDB requires Node.js (v20+) and the filesystem — server-side and desktop only.
Q: What is AxioDBCloud?
TCP-based remote access for AxioDB. Deploy AxioDB in Docker, connect from multiple clients with the exact same API. Supports 1,000+ concurrent connections with auto-reconnect. Optional username/password authentication (TCPAuth: true) reuses the same RBAC accounts as the GUI, and optional TLS encryption (TLS: true) protects the wire protocol on untrusted networks — see AxioDBCloud above.
🤝 Contributing, License & Support
Contributing: we welcome contributions! See CONTRIBUTING.md for guidelines.
License: MIT. See LICENSE.
Requirements: Node.js ≥20.0.0, npm ≥6.0.0, yarn ≥1.0.0 (optional).
Documentation website: built with React 18 + TypeScript, Vite, TailwindCSS, and Lucide React. To run it locally:
cd Document
npm install
npm run devAvailable at http://localhost:5173.
Author: Ankan Saha
Support the project:
- ⭐ Star the repository
- 🐛 Report issues
- 💡 Suggest features
- 🤝 Contribute code
- 💰 Sponsor the project
Acknowledgments: special thanks to all contributors and supporters of AxioDB — your feedback and contributions make this project better.
