Saltar al contenido

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.

school Curso completo

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.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje