npm package: @rckflr/easydb-server
Description: MongoDB-compatible server powered by EasyDB — use any backend (SQLite, PostgreSQL, Memory) with standard MongoDB clients
Author: Mauricio Perera
Latest version: 0.4.0

# EasyDB Server

A **MongoDB-compatible server** powered by [EasyDB](https://github.com/MauricioPerera/easydb). Connect with any standard MongoDB client and store data on your choice of backend.

```
mongosh ──┐
pymongo ──┤              ┌─────────┐
          ├── TCP:27017 ─│ EasyDB  │── SQLite
Compass ──┤              │ Server  │── PostgreSQL
          ├──────────────│         │── Memory
mongoose ─┘              └─────────┘── Redis, MySQL, Turso...
```

## Why?

**FerretDB** gives you MongoDB compatibility on PostgreSQL. EasyDB Server gives you MongoDB compatibility on **any backend** — SQLite, PostgreSQL, MySQL, Redis, in-memory, and more. One server, 10+ storage engines.

This project serves as a real-world showcase of [EasyDB](https://github.com/MauricioPerera/easydb)'s universal storage abstraction.

## Quick Start

```bash
# Run with in-memory storage (no persistence)
npx @rckflr/easydb-server

# Run with SQLite (persistent)
npx @rckflr/easydb-server --adapter sqlite --data ./mydata

# Then connect with mongosh
mongosh mongodb://localhost:27017
```

```javascript
// Or connect with the Node.js driver
import { MongoClient } from 'mongodb';

const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('test');

await db.collection('users').insertOne({ name: 'Alice', age: 30 });
const user = await db.collection('users').findOne({ name: 'Alice' });
console.log(user); // { _id: ObjectId('...'), name: 'Alice', age: 30 }
```

## Installation

```bash
npm install -g @rckflr/easydb-server

# SQLite support (recommended for persistent storage)
npm install -g @rckflr/easydb-server better-sqlite3

# PostgreSQL support
npm install -g @rckflr/easydb-server pg
```

## CLI Usage

```bash
easydb-server [options]

Options:
  --port, -p <port>           Port to listen on (default: 27017)
  --host <host>               Host to bind to (default: 0.0.0.0)
  --adapter, -a <name>        Storage backend (default: memory)
  --data, -d <path>           Data directory (for sqlite)
  --connection-string <url>   Database URL (for postgres)
  --debug                     Enable debug logging
  --quiet                     Only show warnings and errors
  --help                      Show help
```

### Examples

```bash
# In-memory (great for testing)
easydb-server

# SQLite with persistent storage
easydb-server --adapter sqlite --data ./data

# SQLite on a custom port
easydb-server -a sqlite -d ./data -p 27018

# PostgreSQL
easydb-server --adapter postgres --connection-string "postgresql://user:pass@localhost:5432/mydb"

# Debug mode (see every command)
easydb-server --adapter sqlite --data ./data --debug
```

## Storage Backends

| Backend | Flag | Persistence | Best For |
|---------|------|------------|----------|
| **Memory** | `--adapter memory` | No | Testing, development, CI |
| **SQLite** | `--adapter sqlite --data ./path` | Yes | Single-server, embedded, edge |
| **PostgreSQL** | `--adapter postgres --connection-string "..."` | Yes | Production, multi-tenant |

All backends are powered by [EasyDB adapters](https://github.com/MauricioPerera/easydb) and can be swapped with a single CLI flag — your application code doesn't change.

### SQLite Persistence

Data is stored in per-collection `.sqlite` files:

```
./data/
├── _easydb_meta___global.sqlite   # Collection registry
├── test.users.sqlite               # db.users collection
├── test.products.sqlite            # db.products collection
└── myapp.orders.sqlite             # myapp.orders collection
```

Collections are automatically restored on server restart.

## Compatibility

### Supported Operations

| Operation | Status | Examples |
|-----------|--------|---------|
| **insertOne / insertMany** | Full | `db.users.insertOne({name: "Alice"})` |
| **find / findOne** | Full | `db.users.find({age: {$gt: 25}})` |
| **updateOne / updateMany** | Full | `db.users.updateOne({name: "Alice"}, {$set: {age: 31}})` |
| **deleteOne / deleteMany** | Full | `db.users.deleteOne({name: "Alice"})` |
| **countDocuments** | Full | `db.users.countDocuments({role: "admin"})` |
| **distinct** | Full | `db.users.distinct("role")` |
| **findOneAndUpdate** | Full | `db.users.findOneAndUpdate(filter, update, {returnDocument: "after"})` |
| **findOneAndDelete** | Full | `db.users.findOneAndDelete({name: "Alice"})` |
| **sort / skip / limit** | Full | `db.users.find().sort({age: -1}).skip(10).limit(5)` |
| **projection** | Full | `db.users.find({}, {name: 1, email: 1})` |
| **aggregate** | Basic | `$match`, `$sort`, `$limit`, `$skip`, `$project`, `$group`, `$count`, `$unwind`, `$set` |
| **createIndex** | Stub | Acknowledged but not enforced |
| **show dbs / collections** | Full | `show dbs`, `show collections` |
| **drop / dropDatabase** | Full | `db.users.drop()`, `db.dropDatabase()` |

### Query Operators

| Category | Operators |
|----------|----------|
| **Comparison** | `$eq` `$ne` `$gt` `$gte` `$lt` `$lte` `$in` `$nin` |
| **Logical** | `$and` `$or` `$not` `$nor` |
| **Element** | `$exists` `$type` |
| **Array** | `$size` `$all` `$elemMatch` |
| **Evaluation** | `$regex` `$mod` |
| **Implicit** | `{field: value}` (exact match, works with arrays) |

### Update Operators

| Operator | Description |
|----------|-------------|
| `$set` | Set field value |
| `$unset` | Remove field |
| `$inc` | Increment number |
| `$mul` | Multiply number |
| `$min` / `$max` | Set if less/greater than current |
| `$push` | Append to array (supports `$each`) |
| `$pull` | Remove from array |
| `$addToSet` | Append unique to array |
| `$pop` | Remove first/last from array |
| `$rename` | Rename field |
| `$currentDate` | Set to current date |

### Aggregation Pipeline

| Stage | Status |
|-------|--------|
| `$match` | Full |
| `$sort` | Full |
| `$skip` / `$limit` | Full |
| `$project` | Full |
| `$count` | Full |
| `$group` | `$sum`, `$avg`, `$min`, `$max`, `$push`, `$addToSet`, `$first`, `$last` |
| `$unwind` | Full |
| `$set` / `$addFields` | Basic (field references only) |
| `$lookup` | Not supported |
| `$facet` | Not supported |

### Not Supported

These MongoDB features are intentionally out of scope:

- Transactions (multi-document)
- Change streams
- Aggregation: `$lookup`, `$facet`, `$bucket`, `$graphLookup`
- Full-text search (`$text`)
- Geospatial queries
- Authentication (SCRAM-SHA-256)
- Replication / Sharding
- GridFS
- Wire compression

## Architecture

```
┌─────────────────────────────────────────┐
│            MongoDB Client               │
└──────────────┬──────────────────────────┘
               │ TCP (MongoDB Wire Protocol)
               │ OP_MSG + OP_QUERY (BSON)
               ▼
┌─────────────────────────────────────────┐
│          EasyDB Server                  │
│                                         │
│  Wire Protocol ─→ Command Router        │
│                      │                  │
│              ┌───────┼────────┐         │
│              ▼       ▼        ▼         │
│          Handshake  CRUD    Admin       │
│                      │                  │
│              Query Engine               │
│          (matcher + updater)            │
│                      │                  │
│            DatabaseManager              │
│                      │                  │
│              EasyDB Core                │
│         .put() .get() .where()          │
└──────────────┬──────────────────────────┘
               │
       ┌───────┼───────┐
       ▼       ▼       ▼
    SQLite  Postgres  Memory  ...
```

## Use Cases

- **Development**: Drop-in MongoDB replacement that requires zero setup
- **Testing**: Ephemeral in-memory database for CI/CD pipelines
- **Edge/Embedded**: Run a MongoDB-compatible database on devices with SQLite
- **Prototyping**: Start with SQLite, switch to PostgreSQL for production
- **Learning**: Understand how MongoDB wire protocol works under the hood

## Docker

```bash
# Build
docker build -t easydb-server .

# Run with in-memory
docker run -p 27017:27017 easydb-server

# Run with SQLite persistence
docker run -p 27017:27017 -v ./data:/data easydb-server \
  --adapter sqlite --data /data
```

Or with docker-compose:

```yaml
services:
  easydb:
    build: .
    ports:
      - "27017:27017"
    volumes:
      - ./data:/data
    command: ["--adapter", "sqlite", "--data", "/data"]
```

## Comparison with Alternatives

| Feature | EasyDB Server | FerretDB | MongoDB |
|---------|:------------:|:--------:|:-------:|
| Storage backends | **10+** | 2 | 1 |
| SQLite backend | Yes | Yes | No |
| PostgreSQL backend | Yes | Yes | No |
| MySQL/Redis/KV backend | Yes | No | No |
| In-memory mode | Yes | No | No |
| Zero config startup | Yes | No | Yes |
| npm installable | Yes | No | No |
| Docker image size | ~50MB | ~100MB | ~700MB |
| CRUD operations | Full | Full | Full |
| Aggregation | Basic | Most | Full |
| Transactions | No | Partial | Full |
| Auth/TLS | No | Yes | Full |
| License | MIT | Apache 2.0 | SSPL |

## Contributing

PRs welcome! See [SPEC.md](SPEC.md) for the full technical specification.

## License

MIT