
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Offline-first MongoDB sync — Local + Atlas feel like ONE database. Automatic conflict resolution, Mongoose plugin, interactive CLI, zero boilerplate.
Offline-first MongoDB sync — Local + Atlas feel like ONE database. Drop-in production setup, automatic connection management, zero boilerplate.
MongoFire keeps a local MongoDB and MongoDB Atlas in sync — automatically, reliably, and with zero boilerplate. Your app reads and writes to a local MongoDB instance that is always fast and always available, even when offline. MongoFire handles the rest in the background.
⚡ MongoFire manages every MongoDB connection for you. You never call
mongoose.connect(). You never callapp.listen()directly. You import two functions —startAppandplugin— and everything else is automatic.
Features:
startApp(app, port) — replaces app.listen(), waits for DB, then opens the servernpm install mongofire
Peer dependencies (install once in your project):
npm install mongodb mongoose dotenv
npx mongofire init
This creates three files in your project root:
| File | Purpose |
|---|---|
.env | MongoDB connection strings |
mongofire.config.js | Collections to sync, intervals, options |
mongofire.js | The MongoFire entry point — do not delete |
.envATLAS_URI=mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net/
LOCAL_URI=mongodb://127.0.0.1:27017
DB_NAME=myapp
ATLAS_URIis optional — omit it to run in local-only mode during development.
server.js — your Express entry point:
// ESM
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import { startApp } from './mongofire.js'; // ← import from YOUR mongofire.js
import authRoutes from './routes/auth.routes.js';
import studentRoutes from './routes/student.routes.js';
const app = express();
app.use(express.json());
app.use(cookieParser());
app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true }));
app.use('/auth', authRoutes);
app.use('/students', studentRoutes);
// ✅ Replaces app.listen() — waits for local DB then starts the server
startApp(app, process.env.PORT || 3000);
// CommonJS
const express = require('express');
const { startApp } = require('./mongofire');
const app = express();
app.use(express.json());
app.use('/auth', require('./routes/auth.routes'));
startApp(app, process.env.PORT || 3000);
models/User.js — attach the plugin to your Mongoose schemas:
// ESM — from a file inside models/
import mongoose from 'mongoose';
import { plugin } from '../mongofire.js'; // ← note: ../ because models/ is one level deep
const UserSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
updatedAt: Date,
});
UserSchema.plugin(plugin('users')); // collection name must match mongofire.config.js
export default mongoose.model('User', UserSchema);
// CommonJS — from a file inside models/
const mongoose = require('mongoose');
const { plugin } = require('../mongofire'); // ← note: ../ one level up
const StudentSchema = new mongoose.Schema({
name: String,
grade: Number,
updatedAt: Date,
});
StudentSchema.plugin(plugin('students'));
module.exports = mongoose.model('Student', StudentSchema);
Critical: The import path is always a relative path to
mongofire.jsin your project root. It is never'mongofire'(the npm package name). Fromserver.js(root) →'./mongofire.js'Frommodels/User.js(one level deep) →'../mongofire.js'
backend/
├── mongofire.js ← Generated by init — the bridge between your app and MongoFire
├── mongofire.config.js ← Your sync configuration
├── .env ← Connection strings (never commit this)
├── server.js ← import { startApp } from './mongofire.js'
├── models/
│ ├── User.js ← import { plugin } from '../mongofire.js'
│ └── Student.js ← import { plugin } from '../mongofire.js'
└── routes/
├── auth.routes.js
└── student.routes.js
startApp() doesstartApp(app, port) replaces app.listen(). It:
app.listen(port) only after the DB is ready🚀 [MongoFire] Server ready on port <port> on successConsole output on startup:
✅ [MongoFire] Local MongoDB connected
🚀 [MongoFire] Server ready on port 3000
🌐 [MongoFire] Atlas connected — sync active
🔄 [MongoFire] Sync complete — ↑2 uploaded ↓5 downloaded 🗑 0 deleted
mongofire.config.js)export default {
localUri: process.env.LOCAL_URI || 'mongodb://127.0.0.1:27017',
atlasUri: process.env.ATLAS_URI,
dbName: process.env.DB_NAME || 'myapp',
collections: ['users', 'students', 'orders'], // every collection your app uses
syncInterval: 30000, // ms between sync cycles (minimum 500)
batchSize: 200,
syncOwner: '*', // '*' = sync all | 'userId' = per-user isolation
realtime: false, // true = Atlas Change Streams
cleanDays: 7,
onSync(result) {
if (result.uploaded + result.downloaded + result.deleted > 0)
console.log(`Synced: ↑${result.uploaded} ↓${result.downloaded}`);
},
onError(err) { console.error('Sync error:', err.message); },
};
| Option | Type | Default | Description |
|---|---|---|---|
collections | string[] | required | Collection names to sync |
localUri | string | 'mongodb://127.0.0.1:27017' | Local MongoDB URI |
atlasUri | string | null | Atlas URI. Omit for local-only mode |
dbName | string | 'myapp' | Database name |
syncInterval | number | 30000 (5000 if realtime:true) | Polling interval in ms (minimum: 500) |
batchSize | number | 200 | Documents per batch (1–10 000) |
syncOwner | string|fn | '*' | Owner filter — see Multi-Tenant section |
realtime | boolean | false | Enable Atlas Change Streams |
cleanDays | number | 7 | Auto-clean synced records older than N days |
onSync | function | null | Called after each sync cycle |
onError | function | null | Called when a sync cycle throws |
reconcileOnStart | boolean | true | Scan for lost writes at startup |
import { plugin } from 'mongofire'; // ❌ ERR_MODULE_NOT_FOUND
const { plugin } = require('mongofire'); // ❌ wrong unless mongofire is installed globally
// server.js (same folder as mongofire.js)
import { startApp, plugin } from './mongofire.js';
// models/User.js (one folder deep)
import { plugin } from '../mongofire.js';
// routes/user.routes.js (one folder deep)
import { plugin } from '../mongofire.js';
import { mongofire } from './mongofire.js';
mongofire.on('localReady', () => console.log('Local DB connected'));
mongofire.on('online', () => console.log('Atlas connected'));
mongofire.on('offline', () => console.log('Working offline'));
mongofire.on('sync', (r) => console.log('Synced:', r));
mongofire.on('conflict', (c) => console.warn('Conflict:', c));
mongofire.on('error', (e) => console.error('Error:', e));
mongofire.on('stopped', () => console.log('Shut down cleanly'));
| Event | Payload | When emitted |
|---|---|---|
localReady | Db | Local MongoDB connected (before Atlas) |
ready | — | start() fully completed |
online | — | Atlas connected |
offline | — | Atlas becomes unreachable |
sync | SyncResult | After each sync cycle |
conflict | ConflictData | Local write conflicts with remote |
conflictResolved | { opId, resolution } | After retry or dismiss |
stopped | — | stop() finished |
error | Error | Unexpected sync error |
startApp(app, port) → Promise<http.Server>Replaces app.listen(). Waits for local DB, then opens the server port. Exits with code 1 on DB failure.
import { startApp } from './mongofire.js';
startApp(app, process.env.PORT || 3000);
plugin(collectionName, options?) → Mongoose plugin functionAttaches change-tracking to a schema. Apply before mongoose.model().
import { plugin } from '../mongofire.js';
UserSchema.plugin(plugin('users'));
UserSchema.plugin(plugin('users', { ownerField: 'userId' })); // multi-tenant
localReady → Promise<Db>Resolves as soon as local MongoDB is connected, before Atlas.
import { localReady } from './mongofire.js';
await localReady; // DB is guaranteed ready after this
ready → Promise<MongoFire>Resolves after Atlas connect and the first sync.
mongofire.sync(type?) → Promise<SyncResult>Manually trigger a sync ('required' or 'all').
mongofire.status() → Promise<SyncStatus>Returns { online, pending, creates, updates, deletes, realtime }.
mongofire.conflicts() / retryConflict(opId) / dismissConflict(opId)View and resolve sync conflicts.
mongofire.reconcile(opts?) → Promise<ReconcileResult[]>Scan for and recover writes lost in a crash.
mongofire.resetLocal() → Promise<{ dropped: number }>Wipe the local DB. Next startup re-bootstraps from Atlas. Unsynced changes are lost.
mongofire.stop(timeoutMs?) → Promise<void>Flush in-flight ops and close connections. Called automatically on SIGINT/SIGTERM.
// mongofire.config.js
export default {
atlasUri: process.env.ATLAS_URI,
collections: ['orders'],
realtime: true, // requires Atlas M10+ or a local replica set
syncInterval: 5000, // polling fallback
};
Falls back to polling if Change Streams are unavailable. Saves a resume token — restarts pick up exactly where they left off. Restarts use exponential backoff (2 s → 60 s).
For apps where each user must only sync their own data:
// mongofire.config.js
export default {
collections: ['notes'],
syncOwner: () => currentUserId, // returns the current user's ID
};
// model
NoteSchema.plugin(plugin('notes', { ownerField: 'userId' }));
// create — always set the owner field
await Note.create({ title: 'My note', userId: req.user._id });
npx mongofire init # Setup wizard (creates mongofire.js, config, .env)
npx mongofire init --force # Overwrite existing files
npx mongofire init --esm # Force ESM output
npx mongofire init --cjs # Force CJS output
npx mongofire config # Update config interactively
npx mongofire status # Show pending sync counts
npx mongofire clean --days=7 # Delete records older than 7 days
npx mongofire conflicts # View and resolve conflicts
npx mongofire reconcile # Recover writes lost from crashes
npx mongofire reconcile --collection=users # Single collection
npx mongofire reset-local # Wipe local DB and re-bootstrap
Set
MONGOFIRE_DEBUG=1for full error stack traces.
import { startApp, plugin, mongofire } from './mongofire.js';
import type { SyncResult, ConflictData } from 'mongofire';
UserSchema.plugin(plugin('users'));
startApp(app, 3000);
mongofire.on('sync', (result: SyncResult) => {
console.log(`↑${result.uploaded} ↓${result.downloaded}`);
});
| Variable | Default | Description |
|---|---|---|
ATLAS_URI | — | MongoDB Atlas connection string |
LOCAL_URI | mongodb://127.0.0.1:27017 | Local MongoDB URI |
DB_NAME | myapp | Database name |
MONGOFIRE_DEBUG | unset | Set to 1 for full stack traces |
MONGOFIRE_VERIFY_REMOTE | 0 | Set to 1 to checksum-verify each uploaded document |
MONGOFIRE_COLLECTION_CONCURRENCY | 4 | Collections synced in parallel (max 32) |
MONGOFIRE_DBPATH | ~/.mongofire/<dbName> | Data directory for auto-spawned mongod |
ERR_MODULE_NOT_FOUND: Cannot find package 'mongofire'You are importing from 'mongofire' instead of from './mongofire.js'.
// ❌ Wrong
import { plugin } from 'mongofire';
// ✅ Correct (from project root)
import { plugin } from './mongofire.js';
// ✅ Correct (from models/ folder)
import { plugin } from '../mongofire.js';
mongoose.connect() should not be calledRemove any mongoose.connect() calls from your code. MongoFire manages the connection through localUri in mongofire.config.js.
Local MongoDB failed to connectmongod is not in your system PATHLOCAL_URI in .env is incorrectMongoFire tries to auto-spawn mongod. Set MONGOFIRE_DBPATH to a writable directory if the default fails.
Cannot find module './mongofire.config.js'Run npx mongofire init to regenerate the config file.
MIT — see LICENSE
FAQs
Offline-first MongoDB sync — Local + Atlas feel like ONE database. Automatic conflict resolution, Mongoose plugin, interactive CLI, zero boilerplate.
We found that mongofire demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.