Loading...

Postulate is the best way to take and share notes for classes, research, and other learning.

More info

Making MongoDB Schema Changes with migrate-mongo

Profile picture of Samson ZhangSamson Zhang
Mar 6, 2021Last updated Mar 10, 20213 min read

When developing an application, it's common to make changes to the database schema as you go. In a relational database, such a change necessarily modifies all previously entered rows of data; in a database managed through an ORM, there's likely an automatic schema migration feature built in.

MongoDB, however, is document-based rather than relational, meaning that different documents in a collection can have different fields. Furthermore, it doesn't use an ORM by default, and even mongoose doesn't have easy built in migration features.

This means that when you want to add a field to a set of documents -- for example, the recent addition of the privacy field to the Post model in Postulate -- you have to make sure you're handling migrations correctly, i.e. ensuring that old documents get new fields added to them.

Thankfully, a library called migrate-mongo makes these manual migrations fairly painless. Here's how to use it to make a schema migration.

1. Install the package

Run npm install -g migrate-mongo.

2. Set up configuration

Run migrate-mongo init in your project directory to create a migrate-mongo-config.js file and migrations directory.

I replace the config file's contents with the following, using dot-env to securely load in my MongoDB URI with password included:

require("dotenv").config(); const config = { mongodb: { url: process.env.MONGODB_URL, options: { useNewUrlParser: true, useUnifiedTopology: true, } }, migrationsDir: "migrations", changelogCollectionName: "changelog", migrationFileExtension: ".js" }; module.exports = config;

3. Create migration

Run migrate-mongo create [migration-name] to create a new migration file in the migrations folder. This file exports two functions, up and down. up should contain the code to carry out the operation, while down should contain the code to revert it.

When developing fast, I often don't even write a down function, and have only a very straightforward up function to be carried out across all existing documents in a collection. To add a field with an empty array as the default value, for example, I used the following code:

const mongoose = require("mongoose"); module.exports = { async up(db, client) { await db.collection("snippets").updateMany({}, { $set: { linkedPosts: [] }, }) }, };

For the privacy field example mentioned above, I set the field to a fixed default value:

const mongoose = require("mongoose"); module.exports = { async up(db, client) { await db.collection("posts").updateMany({}, { $set: { privacy: "public" }, }) }, };

In a more complicated migration, I turned embedded documents into standalone ones in a separate collection:

module.exports = { async up(db, client) { const users = db.collection("users"); const usersCursor = await users.find(); while (await usersCursor.hasNext()) { const userData = await usersCursor.next(); if (userData.updates) { for (let update of userData.updates) { await db.collection("updates").insertOne({ ...update, userId: userData._id, }); } await db.collection("users").updateOne({id: userData._id}, { $set: {updates: []}, }); } } }, };

4. Run migration

Once you've created your migration functions, run migrate-mongo up to run them.

This carries out the migration, and also creates a new document in a changelog collection in MongoDB. This document allows the migration to be reverted using migrate-mongo down [options]. It also allows you to re-run a migration script by simply deleting the corresponding document and re-running migrate-mongo up.



With that, you can make clean and complex if needed migrations when making changes to your MongoDB schema!


Comments (loading...)

Sign in to comment

Postulate

Founder and dev notes from building Postulate