Simplified MongoDB Transactions in Express.js
Beginner's Guide to Implementing MongoDB Transactions in Express.js
Introduction
MongoDB is a popular NoSQL database known for its flexibility and scalability. With the introduction of multi-document transactions in MongoDB 4.0, developers gained more power to perform complex operations reliably. In this blog post, we will delve into MongoDB transactions, explore their significance, and provide detailed examples for better understanding.
Understanding Transactions
In database management, a transaction is a sequence of operations that are treated as a single unit of work. These operations either succeed together or fail together. Transactions ensure data integrity by maintaining the ACID properties: Atomicity, Consistency, Isolation, and Durability.
Transactions are essentially sequences of database operations that are treated as a single unit. They adhere to the ACID properties (Atomicity, Consistency, Isolation, Durability):
Atomicity: Either all operations within a transaction succeed, or none of them do. This prevents partial updates that could leave your data in an inconsistent state.
Consistency: Transactions maintain the validity of your database schema. They ensure that data transitions from one consistent state to another.
Isolation: Transactions execute in isolation from other concurrent operations, safeguarding data integrity and preventing unintended modifications during the transaction's execution.
Durability: Once a transaction commits successfully, the changes are persisted durably to the database, guaranteeing their survival even in the event of system failures.
Why Use Transactions?
While MongoDB excels at storing related data within documents, ensuring consistency across multiple documents in separate collections can be challenging without transactions. Consider a scenario where you need to create a new user account:
Insert a document into the
users
collection containingname
,email
, andphone
information.Insert a separate document into the
address
collection storingaddress1
,address2
,city
, andpincode
details.
If these operations were not part of a transaction, there's a potential for inconsistency. Imagine a situation where the user document is inserted into users
but a system crash occurs before the corresponding address document is added to address
. This would leave your data in an incomplete and inaccurate state.
Without transactions, if an error occurs after adding the user's information but before storing their address, the database would be left in an inconsistent state. Transactions help address this issue by ensuring that either all operations related to adding a user succeed or none of them do.
Transactions address this by ensuring that both operations succeed or fail together. If an error occurs during either insertion, the entire transaction rolls back, reverting any changes made. This guarantees data consistency and eliminates the risk of partial updates.
Implementing Transactions in MongoDB
Step 1: Setting Up the Project
Open your terminal.
Create a new directory for your project:
mkdir mongodb-transaction-express
.Navigate into the project directory:
cd mongodb-transaction-express
.Initialize a new Node.js project:
npm init -y
.
Step 2: Installing Dependencies
Install required dependencies: npm install express mongoose
Step 3: Creating the Express Application
Create a new file named
app.js
.Open
app.js
in your code editor.Initialize Express and set up basic middleware:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
Step 4: Setting Up MongoDB Connection
Create a new file named
db.js
.Open
db.js
in your code editor.Set up MongoDB connection using Mongoose:
const mongoose = require('mongoose');
const MONGODB_URI = 'mongodb://localhost:27017/my_database';
mongoose.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.once('open', () => {
console.log('Connected to MongoDB database');
});
module.exports = db;
Step 5: Creating MongoDB Models
Create a new directory named
models
.Inside the
models
directory, create two files:User.js
andAddress.js
.Define schemas and models for
User
andAddress
:models/User.js
:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
trim: true,
unique: true
},
phone: {
type: String,
required: true
}
});
const userModal = mongoose.model('User', userSchema);
module.exports = userModal;
models/Address.js
:
const mongoose = require('mongoose');
const addressSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
unique: true,
},
address1: {
type: String,
required: true
},
address2: {
type: String,
},
city: {
type: String,
required: true
},
pincode: {
type: String,
required: true
}
});
const addressModal = mongoose.model('Address', addressSchema);
module.exports = addressModal;
Step 6: Implementing Routes and Controllers
Create a new directory named
routes
.Inside the
routes
directory, create a file namedindex.js
.Define routes and controllers for creating a new user with address:
routes/index.js
:
const express = require('express');
const router = express.Router();
const db = require('../db');
const userModal = require('../models/User');
const addressModal = require('../models/Address');
router.post('/create-user', async (req, res) => {
const session = await db.startSession();
session.startTransaction();
try {
const { name, email, phone, address1, address2, city, pincode } = req.body;
// Check if the email already exists
const existingUser = await userModal.findOne({ email });
if (existingUser) {
res.status(500).send('User already exists.');
}
// Create user document
const user = await userModal.create([{ name, email, phone }], { session });
// Create address document
await addressModal.create([{ userId: user[0]._id, address1, address2, city, pincode }], { session });
console.log({user})
await session.commitTransaction();
res.status(201).send('User created successfully.');
} catch (error) {
await session.abortTransaction();
console.error('Transaction aborted. Error:', error);
res.status(500).send('Failed to create user.');
}
});
module.exports = router;
Step 7: Mounting Routes in Express Application
Open
app.js
.Mount the routes:
const indexRouter = require('./routes/index');
app.use('/', indexRouter);
Step 8: Running the Express Application
- In the terminal, run the following command:
node app.js
Your Express application is now running on http://localhost:3000
.
Now open your postman or Insomnia to test this endpoint and pass the data and play around, and check if you can send data in only one of the collection or not.
Conclusion
Congratulations🥳! You've successfully implemented MongoDB transactions in an Express.js application. By following this beginner's guide, you've learned how to set up the project, create MongoDB models, define routes and controllers, and run the Express server. Transactions ensure data integrity and consistency, making your application more robust and reliable. Experiment further with MongoDB transactions to enhance your applications even more on the official MongoDB docs.
To read more about tech, web development & open source, you can follow me on Hashnode and Twitter (@MadhuSaini22) and If this blog helped you in any way then you can sponsor my work and show love and support.
Thank you so much for reading! 👩💻