Tech Verse Logo
Enable dark mode
Managing MongoDB Transactions in Laravel: A Practical Guide

Managing MongoDB Transactions in Laravel: A Practical Guide

Tech Verse Daily

Tech Verse Daily

4 min read

MongoDB and Laravel are a powerful combination — Laravel gives you elegant structure, while MongoDB offers flexibility and scalability. But what happens when your operations depend on multiple collections that must update together — for example, a booking confirmation that updates both rooms and guests?

That’s where MongoDB transactions come in. In this article, we’ll explore how to use transactions in Laravel with MongoDB, how they work, and how to use them safely in real-world applications.

What Are Transactions and Why They Matter

A transaction groups multiple operations into one atomic unit — meaning all succeed or all fail.
If something goes wrong halfway through, the transaction rolls back and no partial data is saved.

For example, when a guest books a hotel room, you need to:

  1. Reserve the room.

  2. Create the booking record.

  3. Update the guest’s booking history.

If any of these fail, the others should roll back — otherwise you risk double-booking rooms or missing booking records.

MongoDB Transaction Support

MongoDB introduced multi-document transactions starting with version 4.0, bringing full ACID compliance (Atomicity, Consistency, Isolation, Durability) similar to relational databases.

To use transactions, you must run MongoDB as a replica set (even a single-node replica set works for development).

Laravel + MongoDB Setup

Install the Laravel MongoDB package:

composer require mongodb/laravel-mongodb

Then configure your database connection in config/database.php:

'mongodb' => [
    'driver'   => 'mongodb',
    'dsn'      => env('MONGODB_URI', 'mongodb://127.0.0.1:27017/?replicaSet=rs0'),
    'database' => env('MONGODB_DATABASE', 'hotel_booking_db'),
],

And in your .env:

DB_CONNECTION=mongodb
MONGODB_URI=mongodb://127.0.0.1:27017/?replicaSet=rs0
MONGODB_DATABASE=hotel_booking_db

Real-World Example: Hotel Booking Transaction

Let’s look at a scenario where you’re building a hotel booking system.
When a user books a room, three collections are affected:

  • rooms → mark the room as booked

  • bookings → insert a new booking record

  • guests → append the booking ID to the guest’s record

All of this must happen together — or not at all.

Here’s how you can do it in Laravel:

use Illuminate\Support\Facades\DB;
use MongoDB\BSON\ObjectId;

public function confirmBooking(string $roomId, string $guestId, string $checkIn, string $checkOut)
{
    $connection = DB::connection('mongodb');
    $client = $connection->getMongoClient();
    $db = $connection->getMongoDB();

    $session = $client->startSession();

    try {
        $session->startTransaction();

        $rooms = $db->selectCollection('rooms');
        $bookings = $db->selectCollection('bookings');
        $guests = $db->selectCollection('guests');

        // Check if room is available
        $room = $rooms->findOne(['_id' => new ObjectId($roomId), 'status' => 'available'], ['session' => $session]);

        if (! $room) {
            throw new \Exception('Room not available.');
        }

        // Mark room as booked
        $rooms->updateOne(
            ['_id' => new ObjectId($roomId)],
            ['$set' => ['status' => 'booked']],
            ['session' => $session]
        );

        // Create booking record
        $booking = [
            'room_id' => new ObjectId($roomId),
            'guest_id' => new ObjectId($guestId),
            'check_in' => $checkIn,
            'check_out' => $checkOut,
            'status' => 'confirmed',
            'created_at' => now(),
        ];
        $bookings->insertOne($booking, ['session' => $session]);

        // Add booking reference to guest record
        $guests->updateOne(
            ['_id' => new ObjectId($guestId)],
            ['$push' => ['bookings' => $booking]],
            ['session' => $session]
        );

        $session->commitTransaction();

        return response()->json(['message' => 'Booking confirmed successfully!']);
    } catch (\Exception $e) {
        $session->abortTransaction();
        return response()->json(['error' => $e->getMessage()], 500);
    }
}

If any operation fails, everything rolls back — ensuring data consistency.

Tips and Best Practices

  1. Use transactions only when needed
    MongoDB documents are atomic by default. If all your updates are within a single document, you don’t need a transaction.

  2. Keep transactions short
    The longer a transaction runs, the higher the chance of conflicts or timeouts.

  3. Always handle exceptions
    Wrap your code in try-catch blocks and make sure you commit or abort properly.

  4. Ensure you’re using a replica set
    Transactions won’t work in standalone MongoDB instances.

  5. Index frequently used fields
    Especially fields you use in lookups during transactions, like room_id or guest_id.

Reusable Transaction Service (Clean Code Example)

To keep your controllers clean, you can abstract transaction logic into a service class:

namespace App\Services;

use Illuminate\Support\Facades\DB;
use MongoDB\Client;

class MongoTransactionService
{
    public function run(callable $callback)
    {
        $client = DB::connection('mongodb')->getMongoClient();
        $session = $client->startSession();

        try {
            $session->startTransaction();
            $callback($session);
            $session->commitTransaction();
        } catch (\Throwable $e) {
            $session->abortTransaction();
            throw $e;
        }
    }
}

Usage:

app(MongoTransactionService::class)->run(function ($session) {
    // Your booking or cancellation logic here
});

Wrapping Up

MongoDB transactions give you the best of both worlds — flexibility of NoSQL and reliability of SQL-like atomic operations.

In Laravel, it’s straightforward to use them with the right setup. Whether you’re building an e-commerce platform, a hotel booking app, or any multi-step workflow, MongoDB transactions ensure your data stays consistent and reliable.

    Latest Posts

    View All

    Clean, Reusable Query Logic the Right Way: Laravel Global Scopes & Local Scopes

    Clean, Reusable Query Logic the Right Way: Laravel Global Scopes & Local Scopes

    Mastering Custom Blade Directives in Laravel

    Mastering Custom Blade Directives in Laravel

    Laravel 12.44: Adds HTTP Client afterResponse() Callbacks

    Laravel 12.44: Adds HTTP Client afterResponse() Callbacks

    Laravel Artifact: Manage Your Media Easily

    Laravel Artifact: Manage Your Media Easily

    Handling Large File Uploads in Laravel: A Guide to Chunking & Resuming

    Handling Large File Uploads in Laravel: A Guide to Chunking & Resuming

    Next-Gen Laravel Deployment: FrankenPHP + Octane on Ubuntu VPS

    Next-Gen Laravel Deployment: FrankenPHP + Octane on Ubuntu VPS

    Speed Up Your Laravel App: Mastering Concurrent API Requests with Http::pool and Batch

    Speed Up Your Laravel App: Mastering Concurrent API Requests with Http::pool and Batch

    Beyond the Basics: Building Production-Ready APIs with Laravel

    Beyond the Basics: Building Production-Ready APIs with Laravel

    PHP 8.6: Expected Release Window and RFCs to Watch

    PHP 8.6: Expected Release Window and RFCs to Watch

    Downloading Files from External URLs in Laravel

    Downloading Files from External URLs in Laravel