Transactions are undoubtedly the most exciting new feature in MongoDB 4.0. But unfortunately, most tools for installing and running MongoDB start a standalone server as opposed to a replica set. If you try to start a session on a standalone server, you'll get a "Transaction numbers are only allowed on a replica set member or mongos" error.

const client = await MongoClient.connect('mongodb://localhost:27017/test', {
  useNewUrlParser: true
});

// MongoError: Transaction numbers are only allowed on a replica set member or mongos
const session = client.startSession();
session.startTransaction();

In order to use transactions, you need a MongoDB replica set, and starting a replica set locally for development is an involved process. The new run-rs npm module module makes starting replica sets easy. Running run-rs is all you need to start a replica set, run-rs will even install the correct version of MongoDB for you.

Run-rs has no outside dependencies except Node.js and npm. You do not need to have Docker, homebrew, APT, Python, or even MongoDB installed.

Your First Transaction With Run-rs and MongoDB 4.0

Install run-rs globally with npm's -g flag. You can also list run-rs in your package.json file's devDependencies.

npm install run-rs -g

Next, run run-rs with the --version flag. Run-rs will download MongoDB v4.0.0 for you. Don't worry, it won't overwrite your existing MongoDB install.

run-rs -v 4.0.0 --shell

You should see the below output. Please be patient since MongoDB 4.0.0 is about 70MB.

$ run-rs -v 4.0.0 --shell
Downloading MongoDB 4.0.0
Copied MongoDB 4.0.0 to '/home/node/lib/node_modules/run-rs/4.0.0'
Purging database...
Running '/home/node/lib/node_modules/run-rs/4.0.0/mongod'
Starting replica set...
Started replica set on "mongodb://localhost:27017,localhost:27018,localhost:27019"
Running mongo shell: /home/node/lib/node_modules/run-rs/4.0.0/mongo
rs:PRIMARY>

You now have a replica set running MongoDB 4.0.0 locally. Run rs.status() to verify the replica set is running. Then, you can execute a transaction from the MongoDB shell as shown below.

rs:PRIMARY> db.Answer.insertOne({ answer: 42 })
{
    "acknowledged" : true,
    "insertedId" : ObjectId("5b365814e1610ca91ad0bdc4")
}
rs:PRIMARY> session = db.getMongo().startSession()
session { "id" : UUID("688305ae-afd2-490e-baca-28aa754c01f4") }
@(shell):1:13
rs:PRIMARY> sessionDb = session.getDatabase('test')
rs:PRIMARY> session.startTransaction()
rs:PRIMARY> sessionDb.Answer.deleteOne({ answer: 42 })
{ "acknowledged" : true, "deletedCount" : 1 }
rs:PRIMARY> sessionDb.Answer.find()
rs:PRIMARY> sessionDb.abortTransaction()
rs:PRIMARY>
rs:PRIMARY> sessionDb.Answer.find()
{ "_id" : ObjectId("5b365814e1610ca91ad0bdc4"), "answer" : 42 }

Running a Transaction in Node.js

v3.1.0 of the official MongoDB Node.js driver has full support for transactions. Below is a simple example of executing a transaction in Node.js using async/await.

const assert = require('assert');
const { MongoClient } = require('mongodb');

run().catch(error => console.error(error.stack));

async function run() {
  const uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/test?' +
    'replicaSet=rs';
  const client = await MongoClient.connect(uri, { useNewUrlParser: true });

  const coll = client.db('test').collection('Answer');
  await client.db('test').dropDatabase();
  // You need to explicitly create a collection before starting a transaction
  // Otherwise you'll get an error:
  // "Cannot create namespace test.Answer in multi-document transaction"
  await client.db('test').createCollection('Answer', {});

  const session = client.startSession();
  session.startTransaction();

  // Insert a doc and check that MongoDB stored it
  await coll.insertOne({ answer: 42 }, { session });
  doc = await coll.findOne({}, { session });
  assert.ok(doc);

  // Abort the transaction and undo the write
  await session.abortTransaction();
  session.endSession();

  // Document is no longer there!
  doc = await coll.findOne({});
  assert.ok(!doc);

  console.log('Done');
}

Moving On

Run-rs is the easiest way to get a MongoDB replica set running for local development and testing. Sessions and transactions don't currently work on standalone servers, so homebrew and the official Docker image are not viable for local development if your app uses transactions. Run-rs lets you go from not having MongoDB installed to running a replica set in a single command, so download it and get started using MongoDB transactions!

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus