How to Use Database Transactions With MongoDB and Node.js


Building a production-ready web application requires you to ensure that it’s safe and scalable.


One of the most crucial things to know about databases is the ACID principle which stands for atomicity, consistency, isolation, and durability. Relational databases like MySQL support ACID transactions natively. But MongoDB is a NoSQL database and doesn’t support ACID transactions by default.

As a programmer, you should know how to introduce ACID properties into your MongoDB databases.


What Are Database Transactions?

A database transaction is a sequence of database queries or operations that all execute together as one unit to complete one task.

Database transactions adhere to the concepts of ACID characteristics. This helps to ensure that no changes occur unless all operations are successful. It also ensures the database is consistent.

The ACID Properties Explained

The four properties that make up the ACID principles are:

  • Atomicity is the property that conceptualizes transactions as small units of a program. This implies that all queries either run successfully or fail together.
  • Consistency states that database records must remain consistent before and after every transaction.
  • Isolation ensures that, when multiple transactions run simultaneously, one doesn’t affect the other.
  • Durability focuses on system failures or faults. It ensures that a committed transaction is not lost in the event of a system failure. This may involve techniques necessary to restore data from a backup automatically once the system comes up again.

How to Implement MongoDB Database Transactions in Node.js Using Mongoose

MongoDB has become a widely-used database technology over the years due to its NoSQL nature and flexible document-based model. It also offers you the ability to better organize your data and more flexibly than in SQL or relational databases.

To implement database transactions in MongoDB, you can consider a sample scenario on a job listing application where a user can post, update or delete a job. Here’s a simple database schema design for this application:

Schema diagram for User and Job collections

To follow along, this section requires basic knowledge of Node.js programming and MongoDB.

Having set up a working Node.js and MongoDB project, you can set up a connection to a Mongo database in Node.js. If you haven’t before now, install mongoose by running npm install mongoose in your terminal.

import mongoose from 'mongoose'

let MONGO_URL = process.env.MONGO_URL || 'your-mongo-database-url';

let connection;
const connectDb = async () =>
try
await mongoose.connect(MONGO_URL,
useNewUrlParser: true,
useUnifiedTopology: true,
);

console.log("CONNECTED TO DATABASE");
connection = mongoose.connection;
catch (err)
console.error("DATABASE CONNECTION FAILED!");
console.error(err.message);
process.exit(1);

;

You should store the connection in a variable so you can use it to initiate a transaction later in the program.

You can implement the users and jobs collections like so:

const userSchema = new mongoose.Schema(
name: String,
email: String,
jobs: [mongoose.Schema.Types.ObjectId]
);

const jobSchema = new mongoose.Schema(
title: String,
location: String,
salary: String,
poster: mongoose.Schema.Types.ObjectId
);

const userCollection = mongoose.model('user', userSchema);
const jobCollection = mongoose.model('job', jobSchema);

You can write a function to add a user to the database like this:


const createUser = async (user) =>
const newUser = await userCollection.create(user);
console.log("User added to database");
console.log(newUser);

The code below demonstrates the function to create a job and add it to its poster’s list of jobs using a database transaction.


const createJob = async (job) =>
const userEmail, title, location, salary = job;


const user = await userCollection.findOne( email: userEmail );


const session = await connection.startSession();


try
await session.startTransaction();


const newJob = await jobCollection.create(
[

title,
location,
salary,
poster: user._id,
,
],
session
);
console.log("Created new job successfully!");
console.log(newJob[0]);


const newJobId = newJob[0]._id;
const addedToUser = await userCollection.findByIdAndUpdate(
user._id,
$addToSet: jobs: newJobId ,
session
);

console.log("Successfully added job to user's jobs list");
console.log(addedToUser);

await session.commitTransaction();

console.log("Successfully carried out DB transaction");
catch (e)
console.error(e);
console.log("Failed to complete database operations");
await session.abortTransaction();
finally
await session.endSession();
console.log("Ended transaction session");

;

A create query that runs in a transaction usually takes in and returns an array. You can see this in the code above where it creates newJob and stores its _id property in the newJobId variable.

Here’s a demonstration of how the above functions work:

const mockUser = 
name: "Timmy Omolana",
email: "[email protected]",
;

const mockJob =
title: "Sales Manager",
location: "Lagos, Nigeria",
salary: "$40,000",
userEmail: "[email protected]",
;

const startServer = async () =>
await connectDb();
await createUser(mockUser);
await createJob(mockJob);
;

startServer()
.then()
.catch((err) => console.log(err));

If you save this code and run it using npm start or the node command, it should produce an output like this:

database transactions output

Another way of implementing ACID transactions in MongoDB using Mongoose is by using the withTransaction() function. This approach provides little flexibility as it runs all queries inside a callback function that you pass as an argument to the function.

You could refactor the above database transaction to use withTransaction() like this:

const createJob = async (job) => {
const userEmail, title, location, salary = job;


const user = await userCollection.findOne( email: userEmail );


const session = await connection.startSession();


try
const transactionSuccess = await session.withTransaction(async () =>
const newJob = await jobCollection.create(
[

title,
location,
salary,
poster: user._id,
,
],
session
);

console.log("Created new job successfully!");
console.log(newJob[0]);


const newJobId = newJob[0]._id;
const addedToUser = await userCollection.findByIdAndUpdate(
user._id,
$addToSet: jobs: newJobId ,
session
);

console.log("Successfully added job to user's jobs list");
console.log(addedToUser);
);

if (transactionSuccess)
console.log("Successfully carried out DB transaction");
else
console.log("Transaction failed");

catch (e)
console.error(e);
console.log("Failed to complete database operations");
finally
await session.endSession();
console.log("Ended transaction session");

};

This would produce the same output as the previous implementation. You are free to choose which style to use when implementing database transactions in MongoDB.

This implementation does not use the commitTransaction() and abortTransaction() functions. This is because the withTransaction() function automatically commits successful transactions and aborts failed ones. The only function that you should call in all cases is the session.endSession() function.

Implementing ACID Database Transactions in MongoDB

Database transactions are easy to use when done correctly. You should now understand how Database transactions work in MongoDB and how you can implement them in Node.js applications.

To further explore the idea of ACID transactions and how they work in MongoDB, consider building a fintech wallet or blogging application.


Source link

Leave a Reply

Your email address will not be published. Required fields are marked *