Ready to use UUID in your next laravel app?

UUID stands for Universal Unique Identifier, it has been used to generate a unique id which is a 128 bit long numbers represented by 32 hexadecimal digits. Mostly used in software development to uniquely identify information with no further context.

After generating 1 billion UUIDs every second for the next 100 years, the probability of creating just one duplicate would be about 50%. The probability of one duplicate would be about 50% if every person on earth owns 600 million UUIDs 😇

You can say integerprimary key will be faster for a table than a 32 character long UUID, luckily the performance issue has been solved by spatie/laravel-binary-uuid package, it’s now faster than integer key.

A comparison of the normal ID, binary UUID and optimised UUID approach. Optimised UUIDs outperform all other on larger datasets. by spatie/laravel-binary-uuid
A comparison of the normal ID, binary UUID, and optimised UUID approach. Optimised UUIDs outperform all other on larger datasets. Image by: spatie/laravel-binary-uuid

Now let’s dive into setting up the UUID in our Laravel 5.5 application.

Setup Laravel App

Create a fresh laravel 5.5 app and then pull the uuid package by running composer require spatie/laravel-binary-uuid, since its Laravel 5.5 no need to register this packages service provider in configs/app.php.

Scaffold laravel Auth

We will need to test the user so let’s add the boilerplate for Laravel’s auth system in this app. It’s not required but I like the layout this scaffolding provides just a command. let’s run the php artisan auth:make it will enable registration, login and forgot password features.

Users Migration

We need to make some changes in users migration to use the UUID on users table. First thing let’s swap the id with a uuid, laravel blueprint has uuid() method will use it. Let’s add some extra fields just for demo not required.

Schema::create('users', function (Blueprint $table) {
    $table->uuid('uuid');
    $table->primary('uuid');
    $table->string('name');
    $table->string('email')->unique();
    // just for demo
    $table->string('avatar')->nullable();
    $table->string('city')->nullable();
    $table->string('country')->nullable();
    $table->text('bio')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

Next step is to update fields in the user model factory which will come handy in testing later. Open database/factories/UserFactory.php and paste following.

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        // just for demo
        'city' => $faker->city,
        'country' => $faker->country,
        'avatar' => $faker->imageUrl(100, 100),
        'bio' => $faker->realText(rand(100, 550)),
        'password' => bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});

Enable UUID on User Model

Next, we need to make use of HasBinaryUuid, and HasUuidPrimaryKey trait on our User model to enable UUID second trail as the name suggests is only required if you use UUID as a primary key. By default Eloquent models are set to use int as primary key with auto increment. Open the app/User.php and add traits.

use Spatie\BinaryUuid\HasBinaryUuid;
use Spatie\BinaryUuid\HasUuidPrimaryKey;

class User extends Authenticatable
{
    use Notifiable,
        HasBinaryUuid,
        HasUuidPrimaryKey;
        ...
}

If you wanted to use UUID for more than one Model you should consider creating a Base Model and apply both traits to it, then you can extend this model which needs UUID.

Create UUID

Now we have setup everything in order to use UUID. Create a user to see uuid in action. Remember we have auth scaffolded, so we can visit the registration page and create a new user or you can tinker a new user. Let’s use php artisan tinker and create a new user from there:

>>> $user = factory(App\User::class)->create()
=> App\User {#794
     name: "John Doe",
     email: "you@myapp.in",
     city: "New York",
     country: "United States",
     avatar: "https://lorempixel.com/100/100/?57020",
     bio: "For some minutes the whole party swam to the little passage...",
     uuid: b"\x11çæ\eK\0=Z™Ô¬¼2yÊ\v",
     updated_at: "2017-12-21 06:50:55",
     created_at: "2017-12-21 06:50:55",
   }

As you can see uuid has been created but it’s in binary, to get the UUID as human-readable text you can call $user->uuid_text accessor method on the model:

>>> $user->uuid_text
=> "d57e50e8-e61b-11e7-9be4-acbc3279ca0b"

Getting a User by UUID

Now to query the User by UUID you can use withUuid scope method on the user model:

>>> User::withUuid("d57e50e8-e61b-11e7-9be4-acbc3279ca0b")->first()
=> App\User {#798
     uuid: b"\x11çæ\eÕ~Pè›ä¬¼2yÊ\v",
     name: "John Doe",
     email: "you@myapp.in",
     avatar: "https://lorempixel.com/100/100/?36752",
     city: "New York",
     country: "United States",
     bio: "For some minutes the whole party swam to the little passage...",
     created_at: "2017-12-21 06:54:47",
     updated_at: "2017-12-21 06:54:47",
   }

Similar to Eloquent find($ids) method you can pass multiple uuids to get more than one results:

$models = User::withUuid([
    'ff8683dc-cadd-11e7-9547-8c85901eed2e',
    'ff8683ab-cadd-11e7-9547-8c85900eed2t',
])->get();

That’s it, you have a working uuid implementation in laravel.

Following steps are not required but I am going to update the dashboard to list use and on a link it to open the user details which will be fetched by provided uuid from URL parameter.

Create user profile

Before this we need some dummy users, thanks to Model Factories you can create as many users you want from tinker in one line:

// create 20 users
>>> factory(App\User::class, 20)->create()

Open the HomeController@index and update it with following code:

public function index()
{
    $users = User::paginate();
    return view('home', compact('users'));
}

Next, we need to list all user in home.blade.php, open it and we will use @forelse to loop through the list

<div class="panel-body">
    @if (session('status'))
        <div class="alert alert-success">
            {{ session('status') }}
        </div>
    @endif

    @forelse($users as $user)
    <div class="media">
        <div class="media-left">
            <a href="{{ route('profile', $user->uuid_text) }}">
                <img class="media-object" src="{{ $user->avatar }}" alt="...">
            </a>
        </div>
        <div class="media-body">
            <h4 class="media-heading">
                <a href="{{ route('profile', $user->uuid_text) }}">
                    {{ $user->name }}
                </a>
            </h4>
            <p>{{ str_limit($user->bio) }} </p>
        </div>
    </div>
    @empty
        <p class="text-center">No User found.</p>
    @endforelse
    
    {{ $users->links() }}

</div>

For user profile view I am just going to register a route and return the json of user, if you need you can add a view to format the user’s profile detail.

Update the route uuid/routes/web.php and register a new route:

Route::get('/home', 'HomeController@index')->name('home');
Route::get('/profile/{uiid}', 'HomeController@profile')->name('profile');

We need the profile method on HomeController, let’s create it and return the user:

public function profile($uuid)
{
    $user = User::withUuid($uuid)->firstOrFail();
    return $user;
}

Now you can visit http://localhost:8000/profile/ff8683dc-cadd-11e7-9547-8c85901eed2e to visit users profile page.

Pros & Cons of UUID

You might ask why we should consider uuid in a project, so here are some pros and cons of using a UUID as a primary key.

Pros

  1. UUID are unique across tables, databases, and even servers that allow you to merge rows from different databases or distribute databases across servers.
  2. UUID values do not expose the information about your data so they are safer to use in a URL. For example, if a user with id 2 accesses his account via http://localhost:8000/customers/2 URL, it is easy to guess that there is a customer 3, 4, etc., and this could be a target for an attack.
  3. UUID values can be generated anywhere that avoid a round trip to the database server. It also simplifies logic in the application. For example, to insert data into a parent table and child tables, you have to insert into the parent table first, get generated id and then insert data into the child tables. By using UUID, you can generate the primary key value of the parent table up front and insert rows into both parent and child tables at the same time within a transaction.

Cons

  1. Storing UUID values (16-bytes) takes more storage than integers (4-bytes) or even big integers(8-bytes).
  2. Debugging seems to be  more difficult, imagine the expression WHERE id = 'df3b7cb7-6a95-11e7-8846-b05adad3f0ae' instead of WHERE id = 10
  3. Unfortunately, UUID’s don’t index well due to their size and not being ordered.

So are you going to user uuid in your next project? Let me know in the comments and feel free to ask any help if you needed.