En Laravel, las claves primarias (PK) suelen ser enteros autoincrementales, pero en algunos casos, los UUIDs son una mejor opción. Los UUIDs aportan mayor seguridad en aplicaciones públicas y evitan problemas de colisión en bases de datos distribuidas. En esta guía, te mostraré cómo implementar UUIDs de manera centralizada usando un trait, con un ejemplo práctico que incluye las tablas users, projects, tags y la tabla pivote project_tag.
Creando la Lógica Centralizada: El Trait para UUIDs
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait HasUuid
{
protected static function bootHasUuid(): void
{
static::creating(function ($model) {
if (empty($model->{$model->getKeyName()})) {
$model->{$model->getKeyName()} = (string) Str::uuid();
}
});
}
public function getIncrementing(): bool
{
return false;
}
public function getKeyType(): string
{
return 'string';
}
}Crea varios modelos y migraciones para hacer pruebas
php artisan make:model Project -mf
php artisan make:model Tag -mf
php artisan make:migration create_project_tag_table
Configurando las Migraciones
Las migraciones deben ajustar las claves primarias y foráneas para usar UUIDs.
Tabla users
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}Tabla projects
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('projects', function (Blueprint $table) {
$table->uuid("id")->primary();
$table->foreignUuid('user_id')->constrained();
$table->string("name", 100)->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('projects');
}
}Tabla tags
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTagsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tags', function (Blueprint $table) {
$table->uuid("id")->primary();
$table->string("name", 50)->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tags');
}
}Tabla pivote project_tag
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectTagTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_tag', function (Blueprint $table) {
$table->uuid("id")->primary();
$table->foreignUuid("project_id")->constrained();
$table->foreignUuid("tag_id")->constrained();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_tag');
}
}Configurando los Modelos
Todos los modelos deben usar el trait HasUuid.
Modelo User
<?php
namespace App\Models;
use App\Traits\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
/**
* Class User
* @package App\Models
*/
class User extends Authenticatable
{
use HasUuid, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function projects(): HasMany {
return $this->hasMany(Project::class);
}
}Es importante fijarse cómo hacemos uso del trait HasUuid.
Modelo Project
<?php
namespace App\Models;
use App\Traits\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
class Project extends Model
{
use HasUuid, HasFactory;
/**
* @return BelongsToMany
*/
public function tags(): BelongsToMany {
return $this->belongsToMany(Tag::class, 'project_tag')
->using(new class extends Pivot {
use HasUuid;
});
}
/**
* @return BelongsTo
*/
public function owner(): BelongsTo {
return $this->belongsTo(User::class, "user_id");
}
}Es súper importante fijarse en cómo definimos las relaciones BelongsToMany, aquí aplicamos el trait HasUuid.
Modelo Tag
<?php
namespace App\Models;
use App\Traits\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
class Tag extends Model
{
use HasUuid, HasFactory;
public function projects(): BelongsToMany {
return $this->belongsToMany(Project::class)
->using(new class extends Pivot {
use HasUuid;
});
}
}Creando las Factorías
Factory para Project
<?php
namespace Database\Factories;
use App\Models\Project;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProjectFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Project::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
"name" => $this->faker->domainName,
];
}
}Factory para Tag
<?php
namespace Database\Factories;
use App\Models\Tag;
use Illuminate\Database\Eloquent\Factories\Factory;
class TagFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Tag::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
"name" => $this->faker->text(50),
];
}
}Generar Datos con Seeders
Seeder para la Base de Datos
<?php
namespace Database\Seeders;
use App\Models\Project;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
User::factory(10)->create()->each(function (User $user) {
Project::factory(2)->create([
"user_id" => $user->id,
])->each(function (Project $project) {
Tag::factory(2)->create()->each(function (Tag $tag) use ($project) {
$project->tags()->attach($tag->id);
});
});
});
}
}Conclusión
Implementar UUIDs en Laravel con claves foráneas es sencillo y mejora tanto la seguridad como la flexibilidad de tu aplicación. La introducción de foreignUuid hace que las migraciones sean más claras y manejables. Este enfoque completo, desde migraciones hasta seeders, asegura una estructura de base de datos eficiente y escalable.
¿Te interesa aprender más sobre Laravel? Explora otros artículos en nuestro blog de Laravel o consulta nuestros cursos especializados.
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción