Search code examples
node.jsmongodbtransactions

How to start a transaction in MongoDb?


I'm trying to prevent concurrent requests to a specific record, see the following example:

function addMoney(orderID,orderID){
    const status = Database.getOrder(orderID);

    if (status === 1){
        return "Money Already Added";
    }

    Database.udateOrder(orderID, {status: 1});

    Database.addMoney(userID, 300);

    return true;
}

Assume someone made this request exactly at the same time, therefore the "status" check passed, they'd be able to get Database.addMoney run twice.

Using MySQL, I'd start a transction to lock the row but not sure how to do so using MongoDB.


Solution

  • You can do the transactions in mongodb like MySQL. Consider having an order document with id:123 and status:0. Then you can check for status in a transaction and return if it's already paid or fall through in order to add money document and update order status. If you face any issue like Transaction numbers are only allowed on a replica set member or mongos this link might help.

    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 makes starting replica sets easy.

    const uri = 'mongodb://localhost:27017';
    const dbName = 'myDB';
    const MongoClient = require('mongodb').MongoClient;
    
    async function main() {
    
        const client = new MongoClient(uri);
        await client.connect();
        const session = client.startSession();
        try {
            await session.withTransaction(async () => {
                const orders = client.db(dbName).collection('orders');
                const money = client.db(dbName).collection('money');
                let doc = await orders.findOne({orderID: 123});
                if (doc && doc.status === 1) {
                    console.log("Money Already Added");
                    return
                }
                await orders.updateOne({orderID: 123}, {'$set': {status: 1}});
                await money.insertOne({orderID: 123, userID: 100, amount: 300}, {session});
                console.log("Money added");
            });
            await session.commitTransaction();
        } catch (e) {
            console.log(e);
        } finally {
            await session.endSession();
            await client.close();
        }
    }
    
    main()
    

    The code above may need improvement because I couldn't test it on MongoDB with replica set.