Leaving migrate-mongo when several repos shared its changelog
Switching one repo from migrate-mongo is one command: mmk import copies the history from migrate-mongo's changelog collection into mongo-migrate-kit's.
It gets more interesting when several repos shared that changelog. migrate-mongo has no idea which record belongs to which repo. Everything sits in one changelog collection. Import all of that into one repo and it claims every repo's history as its own.
mongo-migrate-kit 1.5 handles this per repo, using namespaces.
The example
Two repos, users-api and search-api, both ran migrate-mongo against the accounts database. Their shared changelog:
{ fileName: '20250110090000-create-users.js', appliedAt: ISODate('2025-01-10T09:01:00Z') } // users-api
{ fileName: '20250205090000-create-search-index.js', appliedAt: ISODate('2025-02-05T09:01:00Z') } // search-api
{ fileName: '20250301090000-add-email-index.js', appliedAt: ISODate('2025-03-01T09:01:00Z') } // users-apiEach repo installs mongo-migrate-kit 1.5 and sets its namespace before importing:
// users-api/mmk.config.js
module.exports = { dbName: 'accounts', namespace: 'users-api' };Import: each repo takes only its own files
Preview first. mmk matches the changelog against the files in this repo's migrations folder:
$ npx mmk import --dry-run
Loaded config from mmk.config.js
Namespace: users-api
⚠ Skipping 1 record(s) whose files are not in this repo — namespace "users-api" imports only its own migrations: 20250205090000-create-search-index.js
◎ Dry-run Would import 2 record(s) from "changelog" → "_mmk_migrations"
┌───────────────────────────────────┬───────┬─────────────────────┬────────────┐
│ Migration │ Batch │ Applied At │ Checksum │
├───────────────────────────────────┼───────┼─────────────────────┼────────────┤
│ 20250110090000-create-users.js │ 1 │ 2025-01-10 09:01:00 │ recomputed │
├───────────────────────────────────┼───────┼─────────────────────┼────────────┤
│ 20250301090000-add-email-index.js │ 2 │ 2025-03-01 09:01:00 │ recomputed │
└───────────────────────────────────┴───────┴─────────────────────┴────────────┘search-api's record is left alone, and the report names it. Run it for real with npx mmk import.
Then search-api does the same from its own repo, and gets only its own record:
$ npx mmk import
Loaded config from mmk.config.js
Namespace: search-api
⚠ Skipping 2 record(s) whose files are not in this repo — namespace "search-api" imports only its own migrations: 20250110090000-create-users.js, 20250301090000-add-email-index.js
✔ Imported 1 record(s) from "changelog" → "_mmk_migrations"
┌───────────────────────────────────────┬───────┬─────────────────────┬────────────┐
│ Migration │ Batch │ Applied At │ Checksum │
├───────────────────────────────────────┼───────┼─────────────────────┼────────────┤
│ 20250205090000-create-search-index.js │ 1 │ 2025-02-05 09:01:00 │ recomputed │
└───────────────────────────────────────┴───────┴─────────────────────┴────────────┘Each repo now has exactly its own history:
$ npx mmk status # in users-api
Loaded config from mmk.config.js
Namespace: users-api
┌───────────────────────────────────┬─────────┬───────┬─────────────────────┬──────────┬────────┐
│ Migration │ Status │ Batch │ Applied At │ Checksum │ Author │
├───────────────────────────────────┼─────────┼───────┼─────────────────────┼──────────┼────────┤
│ 20250110090000-create-users.js │ applied │ 1 │ 2025-01-10 09:01:00 │ ok │ │
├───────────────────────────────────┼─────────┼───────┼─────────────────────┼──────────┼────────┤
│ 20250301090000-add-email-index.js │ applied │ 2 │ 2025-03-01 09:01:00 │ ok │ │
└───────────────────────────────────┴─────────┴───────┴─────────────────────┴──────────┴────────┘What to know
The order doesn't matter. search-api imported into a collection that already held users-api's records, and didn't need --force. "Is the target empty?" is checked per namespace. Batch numbers are per namespace too, so search-api starts at batch 1.
The old changelog is never modified. mmk import only reads it. If anything goes wrong, the migrate-mongo history is exactly as you left it.
Set the namespace before you import. Import without one and the records land in the default namespace, and you'd then need mmk namespace adopt to move them.
Records whose file is gone stay behind. A migrate-mongo record whose file was deleted from every repo isn't imported anywhere, since no repo can show it's theirs. That's harmless: nothing needs to run for it.
Imported migrations are forward-only. migrate-mongo files use up(db, client), and mongo-migrate-kit calls up({ db, client }). So mmk won't try to run the old files, and refuses to roll them back:
$ npx mmk down
Loaded config from mmk.config.js
Namespace: users-api
✖ Cannot roll back 1 migrate-mongo-imported migration(s): 20250301090000-add-email-index.js
These were adopted via `mmk import` (forward-only). Their files use the positional migrate-mongo signature, which mmk cannot run. Revert them manually or re-author them in mmk format.
✖ MIGRATION_IRREVERSIBLE: Cannot roll back migrate-mongo-imported migration(s): 20250301090000-add-email-index.jsNew migrations are written in mmk's format and can be rolled back normally.
These repos are probably CommonJS. migrate-mongo projects usually are. That's fine: mmk create reads your package.json and writes module.exports files in a CommonJS project. More in JS, TS, ESM or CommonJS.
The checklist
- Upgrade every repo on the database to mongo-migrate-kit 1.5 or newer.
- In each repo: set
namespacein the config file. - In each repo:
npx mmk import --dry-run, read the "Skipping" line, thennpx mmk import. - In each repo:
npx mmk statusshould show only this repo's migrations, allapplied. - Stop running migrate-mongo.
Try it
npm install mongo-migrate-kit mongodb
npx mmk import --dry-runMore in the docs: Moving from migrate-mongo. The package is on npm and GitHub.
Related reading
- Switching from migrate-mongo, step by step: the single-repo version of this guide.
- Many repos, one MongoDB database: why shared histories need namespaces.