When working with databases, ensuring data consistency and integrity is critical — especially in complex applications where multiple operations need to succeed or fail together. Laravel provides a powerful and expressive way to handle this through DB::transaction().
In this article, we’ll explore what DB::transaction() is, when to use it, and how it can prevent data corruption in real-world use cases.
What is DB::transaction()?
In Laravel, DB::transaction() is a method that allows you to execute a group of database operations inside a single transaction. If all operations succeed, the changes are committed to the database. If an exception occurs, everything is automatically rolled back — ensuring your data remains consistent.
<?php
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
// Your database operations here
});
?>
Laravel will automatically rollback if an exception is thrown within the closure.
Why Use Transactions?
Consider the following scenarios:
- You’re saving an order and related order items.
- You're transferring money between two accounts.
- You're creating a user and assigning a role and permissions.
- You’re executing multiple database queries that depend on each other.
In all these cases, a partial update could leave your database in an inconsistent state. That’s exactly what transactions are designed to prevent.
Real-World Use Case: Creating an Order with Items
Imagine you’re building an e-commerce platform. When a user places an order, you need to:
-
Create an order record.
-
Create multiple order item records.
-
Deduct product stock.
If any of these steps fail, you don’t want the order to be saved partially.
Here’s how you can safely handle this with DB::transaction():
<?php
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
DB::transaction(function () use ($request) {
$order = Order::create([
'user_id' => auth()->id(),
'total' => $request->total,
]);
foreach ($request->items as $item) {
$product = Product::findOrFail($item['product_id']);
// Check for stock availability
if ($product->stock < $item['quantity']) {
throw new \Exception("Insufficient stock for {$product->name}");
}
// Create order item
OrderItem::create([
'order_id' => $order->id,
'product_id' => $product->id,
'quantity' => $item['quantity'],
'price' => $product->price,
]);
// Deduct stock
$product->decrement('stock', $item['quantity']);
}
});
?>
If any exception is thrown (e.g., insufficient stock), the transaction is rolled back — no order or order items are saved.
Advanced: Manual Commit and Rollback
Sometimes, you might want more control, such as catching exceptions manually:
<?php
DB::beginTransaction();
try {
// Do something
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Transaction failed: ' . $e->getMessage());
throw $e;
}
?>
This pattern is useful when you want to log specific errors or handle multiple layers of logic conditionally.
Tips & Best Practices
-
Keep the transaction block as small as possible.
-
Avoid HTTP calls, file uploads, or long operations inside the transaction.
-
Always catch and log exceptions when using manual transactions.
-
Use model events (creating, updating, etc.) wisely — they also run inside the transaction.
Testing Transactions
When writing tests, you can wrap them with database transactions too — Laravel does this automatically with RefreshDatabase or DatabaseTransactions traits, rolling back all changes after each test.
Conclusion
DB::transaction() is a vital tool in Laravel to keep your data safe, especially in complex business logic. By wrapping dependent operations into a single transaction, you ensure either everything works — or nothing changes.
Use it wisely, test it well, and it will save you countless debugging hours in production.