In a MERN (MongoDB, Express, React, Node.js) stack application, handling concurrent bookings efficiently is crucial to prevent double bookings and ensure data consistency. Below are some best practices and techniques to handle concurrent bookings effectively.
1. Database-Level Concurrency Handling
a. Use Transactions with MongoDB
MongoDB supports multi-document transactions when using replica sets. This ensures that a booking process is atomic (either fully completes or fully rolls back).
Example using Mongoose Transactions:
const session = await mongoose.startSession();
session.startTransaction();
try {
// Check if the booking slot is available
const existingBooking = await Booking.findOne(
{ eventId: eventId, slotTime: selectedTime },
null,
{ session }
);
if (existingBooking) {
throw new Error("Slot already booked!");
}
// Proceed with the booking
const newBooking = new Booking({
userId,
eventId,
slotTime: selectedTime,
});
await newBooking.save({ session });
// Commit the transaction
await session.commitTransaction();
session.endSession();
return res.status(201).json({ message: "Booking confirmed!" });
} catch (error) {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ error: error.message });
}
✅ Advantages:
- Ensures consistency
- Prevents double booking
- Rolls back changes if any step fails
b. Use Atomic Operators (findOneAndUpdate
with $setOnInsert
)
Another way to handle concurrency without transactions is to use atomic operations.
const booking = await Booking.findOneAndUpdate(
{ eventId: eventId, slotTime: selectedTime },
{ $setOnInsert: { userId, eventId, slotTime: selectedTime } },
{ upsert: true, new: true }
);
if (!booking) {
return res.status(400).json({ error: "Slot already booked!" });
}
✅ Advantages:
- Prevents race conditions
- Efficient for high-traffic booking systems
2. Backend Optimizations for Concurrent Bookings
a. Implement Locking Mechanism (Pessimistic Locking)
- Use a Redis Lock to prevent multiple users from booking the same slot simultaneously.
- Example using
redlock
in Node.js:
const Redlock = require("redlock");
const redis = require("redis");
const client = redis.createClient();
const redlock = new Redlock([client], {
driftFactor: 0.01,
retryCount: 5,
retryDelay: 200,
});
async function bookSlot(userId, eventId, slotTime) {
const lockKey = `lock:event:${eventId}:slot:${slotTime}`;
const lock = await redlock.lock(lockKey, 5000);
try {
const existingBooking = await Booking.findOne({ eventId, slotTime });
if (existingBooking) {
throw new Error("Slot already booked!");
}
const newBooking = new Booking({ userId, eventId, slotTime });
await newBooking.save();
return { message: "Booking confirmed!" };
} finally {
await lock.unlock();
}
}
✅ Advantages:
- Ensures only one request can proceed for a slot at a time
- Ideal for real-time applications
3. Frontend Strategies to Prevent Concurrent Issues
a. Disable the Booking Button After Click
Prevent multiple rapid clicks by disabling the button after submitting the booking request.
<button disabled={isSubmitting} onClick={handleBooking}>
{isSubmitting ? "Processing..." : "Book Now"}
</button>
b. Show Real-Time Slot Availability
Use WebSockets or Polling to update available slots in real time.
const socket = io("http://localhost:5000");
useEffect(() => {
socket.on("slot-updated", (updatedSlots) => {
setAvailableSlots(updatedSlots);
});
return () => socket.off("slot-updated");
}, []);
✅ Advantages:
- Reduces conflicts by showing real-time availability
- Improves user experience
4. Scaling Solutions for High-Traffic Booking Systems
a. Implement a Queue System
If multiple users attempt to book the same slot, you can queue requests using Redis Queue (Bull).
const Queue = require("bull");
const bookingQueue = new Queue("bookingQueue");
bookingQueue.process(async (job) => {
const { userId, eventId, slotTime } = job.data;
return await bookSlot(userId, eventId, slotTime);
});
// Add job to queue
bookingQueue.add({ userId, eventId, slotTime });
✅ Advantages:
- Handles high traffic efficiently
- Ensures sequential processing
Conclusion
To handle concurrent bookings in a MERN stack application:
- Use Transactions to ensure atomicity
- Leverage Atomic Operations (
findOneAndUpdate
) - Implement Redis Locks to avoid race conditions
- Disable Double Clicks on the frontend
- Use WebSockets to update availability in real time
- Implement a Queue System for high-traffic scalability
By combining database-level optimizations, backend strategies, and frontend enhancements, you can prevent double bookings and ensure a smooth user experience. 🚀