Implements UUIDs into your Laravel applications

Andrea Pollastri
3 min readOct 25, 2023

--

UUID (Universally Unique Identifier) is a 128-bit label used for information in computer systems. A normal UUID looks like this:

f081a158-d4b8–42df-aa36–204e0c83b2b4

It’s a 32 hexadecimal (base 16) set of digits, displayed in five groups separated by four hyphens.

There are benefits to using UUIDs:

UUIDs are created by your application, so you don’t have to wait for the database server to create new users.

Since you create your UUIDs independent of the database, you can split your data between multiple database servers.

UUIDs are secure, as they’re randomly generated and unique, so they can’t be guessed.

Any database migration or integration with records and relations will be faster than using simple IDs.

How to integrate it into your Laravel Applications?

The first step is to create UUID structure into your database tables:

// User migration

Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary()->unique()->index();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
// Item migration

Schema::create('items', function (Blueprint $table) {
$table->uuid('id')->primary()->unique()->index();
$table->string('name');
$table->foreignUuid('user_id')->constrained('users');
$table->timestamps();
});

Now we have to create a Trait Class: app/Traits/UUID.php:

<?php

// app/Traits/UUID.php

namespace App\Traits;

use Illuminate\Support\Str;

trait UUID
{
protected static function boot ()
{
// Boot other traits on the Model
parent::boot();

/**
* Listen for the creating event on the user model.
* Sets the 'id' to a UUID using Str::uuid() on the instance being created
*/
static::creating(function ($model) {
if ($model->getKey() === null) {
$model->setAttribute($model->getKeyName(), Str::uuid()->toString());
}
});
}

// Tells the database not to auto-increment this field
public function getIncrementing ()
{
return false;
}

// Helps the application specify the field type in the database
public function getKeyType ()
{
return 'string';
}
}

And use it in our models:

<?php

// app/Models/User.php

namespace App\Models;

use App\Traits\UUID;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, UUID;

protected $fillable = [
'name',
'email',
'password',
];

protected $hidden = [
'password',
'remember_token',
];

protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];

public function items()
{
return $this->hasMany(Item::class);
}
}
<?php

// app/Models/Item.php

namespace App\Models;

use App\Traits\UUID;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Item extends Model
{
use HasFactory, UUID;

public function user()
{
return $this->belongsTo(User::class);
}
}

Finally, we could create some fake data via factories:

<?php

// database/factories/ItemFactory.php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;

class ItemFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'user_id' => User::inRandomOrder()->first()->id,
];
}

public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}
<?php

// database/seeders/DatabaseSeeder.php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
public function run(): void
{
\App\Models\User::factory(10)->create();
\App\Models\Item::factory(100)->create();
}
}

Running migrations and seeders we will obtain a set of data with UUIDs and relations, we could test it with this example code:

// routes/web.php

Route::get('/', function () {
$users = User::all();

return view('welcome')->with('users', $users);
});

// Just for test (implicit UUID routing)
Route::get('/users/{user}', function (User $user) {
return $user;
});
<!-- resources/views/welcome.blade.php -->

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Simple Test</title>
</head>
<body class="container py-5">
<table class="table table-striped table-hover caption-top">
<caption>Users</caption>
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Items</th>
</tr>
</thead>
<tbody>
@forelse($users as $user)
<tr>
<th scope="row">{{ $user->id }}</th>
<td>
<a href="/users/{{ $user->id }}">{{ $user->name }}</a>
</td>
<td>{{ $user->email }}</td>
<td>{{ count($user->items) }}</td>
</tr>
@empty
<p>No data!</p>
@endforelse
</tbody>
</table>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Make your apps more efficient and secure within UUIDs. It’s simple!

--

--