When building web applications in Laravel, two common database challenges often come up:
- Handling soft deletes without breaking unique constraints.
- Supporting multi-tenant uniqueness within a shared database.
Both can cause confusing errors if not handled carefully. In this article, I’ll break down each scenario and show how to solve them effectively.
Scenario 1: Handling Soft Deletes with Unique Constraints
The Problem
Laravel’s soft delete feature keeps deleted records in the database with a deleted_at
timestamp instead of removing them permanently. This is great for data recovery and audit trails.
However, unique constraints on columns like email
or username
don’t ignore soft-deleted rows. So if you try to insert a new user with the same unique value as a soft-deleted user, the database will reject it — even though logically, the user is “deleted.”
How to Handle This
Option A: Restore the soft-deleted record
Before inserting, check if a record with the same unique fields exists but is soft deleted. If yes, restore it:
$user = User::withTrashed()
->where('email', $email)
->first();
if ($user && $user->trashed()) {
$user->restore();
} else {
User::create([
'username' => $username,
'email' => $email,
]);
}
Option B: Use conditional unique indexes
Modify your database indexes to include deleted_at
so uniqueness applies only to non-deleted records:
$table->unique(['email', 'deleted_at']);
This is a bit more advanced and depends on your database engine.
Scenario 2: Handling Multi-Tenant Uniqueness in a Shared Database
The Problem
In multi-tenant apps where multiple businesses share the same database tables, unique fields like email
must be unique within each tenant, not globally.
Without scoping uniqueness by tenant, two tenants could never have users with the same email, even though their data should be isolated.
How to Handle This
Add a tenant_id
(or business_id
) column to your tables and include it in unique indexes:
$table->unsignedBigInteger('tenant_id');
$table->unique(['tenant_id', 'email']);
$table->unique(['tenant_id', 'username']);
This means:
- Two different tenants can have users with the same email.
- Each tenant still enforces uniqueness on their own users.
In your Laravel queries and models, always scope by tenant:
User::where('tenant_id', $tenantId)->where('email', $email)->exists();
Summary
Scenario | Problem | Solution |
---|---|---|
Soft deletes + uniqueness | Unique constraint fails due to soft-deleted rows | Restore soft-deleted record or use conditional unique indexes |
Multi-tenant uniqueness | Uniqueness enforced globally across tenants | Add tenant_id to unique indexes and scope queries by tenant |
Want to See It in Action?
I’ve put together a full Laravel demo project demonstrating these concepts in practice, including:
- Soft delete handling with restore logic
- Multi-tenant uniqueness enforcement
- Example migrations, models, and controller code
Check it out here:
https://github.com/Williamug/testing-unique-handling
Run your server and go to http://127.0.0.1:8000/users
Feel free to clone, experiment, and improve!
Conclusion
Soft deletes and multi-tenant uniqueness are often overlooked pain points in Laravel apps. Applying these simple patterns will save you hours of debugging and improve your app’s reliability.
If you found this article useful, please share or comment
Top comments (0)