Skip to content

Mohcin Bounouara

Thoughts about software engineering and life

How to Add UUIDs to an Existing Laravel Database

Introduction

UUIDs (Universally Unique Identifiers) are 128-bit identifiers that are globally unique, making them ideal for scenarios where you want to hide sequential ID patterns from your users. This article walks you through converting an existing Laravel application from auto-incrementing integer IDs to UUIDs.

Why Use UUIDs?

Pros:

Security: Prevents ID enumeration attacks (users can’t guess `/users/1`, `/users/2`), and can’t guess the first user which is usually the system admin.

Distributed Systems: Generate IDs client-side without database coordination

Merging Data: No ID conflicts when merging databases from different sources

API Design: Cleaner, more secure public-ready identifiers

Scalability: Better for microservices and distributed architectures

Trade-offs (There is always a trade-off):

Storage: UUIDs take 36 characters (string) vs integers (typically 4-8 bytes)

Performance: A bit slower indexing compared to sequential integers, specially when searching data.

Readability: Less human-friendly than sequential numbers

Implementation Steps

Step 1: Create a UUID Trait

First, create a reusable trait that handles UUID generation automatically:

<?php

namespace App\Http\Traits;

use Illuminate\Support\Str;

trait UseUuid
{
    protected static function booted(): void
    {
        static::creating(function ($model) {
            $model->incrementing = false;
            $model->keyType = 'string';
            $model->{$model->getKeyName()} = (string) Str::uuid();
        });
    }

    public function getIncrementing()
    {
        return false;
    }

    public function getKeyType()
    {
        return 'string';
    }
}

What is these built-in function does:

– `booted()`: Hooks into the model lifecycle to generate UUIDs before creation

– `getIncrementing()`: Tells Laravel this model doesn’t use auto-incrementing IDs

– `getKeyType()`: Specifies the primary key is a string, not an integer

Step 2: Update Your Models

Add the trait to all models that need UUIDs:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Http\Traits\UseUuid;

class Post extends Model
{
    use UseUuid;
    
    protected $fillable = [
        'user_id',
        'title',
        'content',
        'status',
    ];
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Apply the same trait to any other models that need it.

Step 3: Update Migration Files

This is the main and important step. You need to update your migration files to use `uuid()` instead of `id()`:

Normally you had Before (without using UUID, Integer IDs):

Something like this:

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

After (when using UUID):

Schema::create('posts', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->foreignUuid('user_id')->constrained()->cascadeOnDelete();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

Step 4: For Pivot Tables:

Don’t forget pivot tables for many-to-many relationships:
You should change it to be something like below

Schema::create('post_tag', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->foreignUuid('post_id')->constrained()->cascadeOnDelete();
    $table->foreignUuid('tag_id')->constrained()->cascadeOnDelete();
    $table->timestamps();
});

Step 6: Fresh Migration

Since you’re changing the fundamental structure, run a fresh migration (DON’T DO IT ON PRODUCTION OR STAGING THIS WILL DROP ALL TABLES) when you finish.

php artisan migrate:fresh

Testing Your Implementation:

// Create a user
$user = User::create([
    'name' => 'Mohcin',
    'email' => 'mochin@example.com',
    'password' => bcrypt('password')
]);

// Check the ID
var_dump($user->id);
// Output: string(36) "3800f3d7-139c-433a-9e6f-a0947411f7a6"

// Create a post with relationship
$post = Post::create([
    'user_id' => $user->id,
    'title' => 'My First Post',
    'content' => 'Hello World!'
]);

var_dump($post->id);
// Output: string(36) "2bebf865-184f-40b6-aabd-426efa0dd833"

// Test relationship
$post->user->name; // "Mohcin"

UUIDs provide better security and scalability for modern applications, especially those with public APIs. While there are trade-offs in storage and performance, the benefits often outweigh the costs for most applications.

Resources:

https://laravel.com/docs/migrations

https://www.rfc-editor.org/rfc/rfc4122