Learn how to use database transactions in Laravel to ensure your data remains consistent. Stop partial updates by mastering atomic operations with DB::transaction.
Previously in this course, we explored how to write feature tests for forms and validation in Introduction to Testing. In this lesson, we shift our focus to data reliability: ensuring that when our application performs multiple related database operations, they either all succeed or all fail together.
In a real-world application, you often need to perform multiple database queries that depend on each other. Imagine our Task Manager: if we were to implement a "Task Log" feature, we might need to create a new task entry and record an audit log entry simultaneously.
If the first query succeeds but the second fails (perhaps due to a disk space issue or a constraint violation), your database ends up in a "partial" or "corrupt" state. You have a task, but no audit record. This is where transactions come in.
A transaction is a sequence of operations performed as a single logical unit of work. To maintain database integrity, transactions follow the principle of atomicity: all operations within the block are committed to the database only if every single one succeeds. If any operation fails, the database rolls back to the state it was in before the transaction started.
Laravel provides a simple, clean interface for handling these scenarios through the DB facade. The most idiomatic way to use it is the transaction method, which automatically handles the BEGIN, COMMIT, and ROLLBACK commands for you.
Here is how you would implement an atomic operation in a controller:
PHPuse Illuminate\Support\Facades\DB; use App\Models\Task; use App\Models\AuditLog; public function store(Request $request) { $validated = $request->validated(); DB::transaction(function () use ($validated) { #6A9955">// 1. Create the task $task = Task::create($validated); #6A9955">// 2. Create the associated audit log AuditLog::create([ 'task_id' => $task->id, 'action' => 'task_created', ]); }); return redirect()->route('tasks.index')->with('success', 'Task created successfully.'); }
If AuditLog::create fails, the Task::create operation is automatically rolled back. Your database remains clean, and the user receives an exception (which you can catch or let bubble up to your global exception handler).
While DB::transaction handles the rollback automatically when an exception is thrown, you might want to perform custom logic if a failure occurs. You can pass a second argument to the transaction method to define the number of attempts if you're worried about database deadlocks, or simply wrap the call in a try-catch block.
PHPtry { DB::transaction(function () { #6A9955">// Complex logic here... }); } catch (\Exception $e) { #6A9955">// Log the error or notify the user return back()->withErrors(['error' => 'Could not save the task.']); }
In our Task Manager project, let's ensure that when we mark a task as "Complete," we also update a completed_at timestamp and increment a user_points count.
TasksController.update call for the Task model and the user's points update inside a DB::transaction block.DB::transaction block. If the transaction rolls back, you cannot "roll back" an email already sent to a user. Keep your transactions focused strictly on database operations.use keyword: When using a closure in DB::transaction, remember to pass variables from the parent scope using the use keyword if you need them inside the transaction.DB::beginTransaction() and DB::commit() manually, it is much safer to use the DB::transaction(callback) closure approach. It eliminates the risk of forgetting to call commit() or rollBack() in complex conditional branches.Transactions are your first line of defense against data corruption. By wrapping related database calls in an atomic block, you ensure that your application's state remains consistent even when things go wrong. Remember: if it's a multi-step update, keep it inside a transaction.
Up next: We will learn about Handling Global Exceptions to manage errors gracefully and provide better feedback to your users.
Learn to use DB::transaction to ensure data integrity in your Laravel apps. Prevent partial state updates by wrapping complex operations in atomic blocks.
Read moreLearn how to display database data in your Laravel Task Manager. We'll connect your Eloquent models to your Blade views to render real, dynamic tasks.
Using Database Transactions