#laravel#database#architecture#mysql#postgresql

Database Constraints in Laravel: Beyond FormRequest Validation

"Why application validation is insufficient: enforce native database constraints in Laravel migrations for absolute data integrity."

By huud
0 views
~2 min read
Database Constraints in Laravel: Beyond FormRequest Validation

Database Constraints in Laravel: Beyond FormRequest Validation

Relying exclusively on application-level validation (FormRequest or $request->validate()) leaves systems vulnerable to race conditions under high concurrency and bypasses direct SQL executions.


1. Race Conditions in the unique Rule

Laravel's unique validation rule executes SELECT COUNT(*) WHERE email = ? before inserting.

php
// Request A and Request B hit the server concurrently within milliseconds
$request->validate(['email' => 'required|unique:users,email']);
  1. Request A runs SELECT COUNT(*), returns 0 (Passes).
  2. Request B runs SELECT COUNT(*), returns 0 (Passes).
  3. Request A executes INSERT INTO users (email) VALUES ('test@mail.com').
  4. Request B executes INSERT INTO users (email) VALUES ('test@mail.com').

Result: Duplicate records exist if there is no unique index at the database engine level.

Fix: Migration Unique Constraint

php
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('email')->unique(); // Native unique index
    $table->timestamps();
});

2. Composite Unique Constraints for Paired Columns

Example: Restricting users to exactly one vote per article.

php
Schema::create('votes', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    $table->timestamps();

    // Enforce unique pair
    $table->unique(['user_id', 'post_id']);
});

3. Check Constraints for Numeric Integrity

Instead of relying solely on validation rules like min:0, enforce Check Constraints in migrations (supported in MySQL 8.0+, PostgreSQL, and SQLite).

php
Schema::create('wallets', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->bigInteger('balance')->default(0);
    $table->timestamps();

    // Prevent negative balance directly at DB storage engine
    $table->check('balance >= 0');
});

4. Foreign Key Cascades

Use native cascading foreign keys to ensure referential integrity during entity deletions:

php
$table->foreignId('user_id')
    ->constrained()
    ->cascadeOnDelete();

Summary

  • Application validation (FormRequest) provides clear UX error messages.
  • Database Constraints (UNIQUE, CHECK, FOREIGN KEY) guarantee data integrity against concurrent race conditions and application-level bugs.

About the Author

huud

huud

@huud

About →

Systems architect and software engineer building high-performance distributed platforms.