UUIDs for User IDs
Tags: tipstricks, elixir, phoenix, pow
Published: 2021-02-04 17:00:46

---
title: UUIDs for User IDs
published: true
date: 2021-01-30 20:59:25 UTC
tags: TipsTricks,elixir,phoenix,pow
canonical_url: https://mrmurphy.dev/uuids-for-user-ids/
---

## Integers By Default 🔢

[Phoenix's](http://www.phoenixframework.org) generators save a ton of time writing boilerplate code. [Pow](https://powauth.com) is an Elixir package and Phoenix extension that offers a great way to get user authentication up and running in very little time. But by default Phoenix's generators use auto-incrementing integers for user IDs.

## What's Wrong with Integer IDs 🤔?

I've been bitten nastily by integer IDs multiple times in my career.

One time a backup failure caused the counter on user IDs to be reset, and the account records were erased while the data still existed, un-attached. When new accounts got created, those IDs got re-used, and existing data got attached to the new accounts, super bad news. (This is also an argument for using a SQL store with strong relational guarantees, rather than the NoSQL store the company had adopted at the time).

On another occasion, user IDs were being randomly generated, but they were still rather small integers, and the likelihood of a collision was high due to the rate at which new accounts were being created. A simple oversight in the application code was performing an _upsert_ on the newly generated user accounts instead of an _insert._ The Database would have detected a conflict and thrown an error in the case of an insert on a re-used user ID, but since the app was upserting, re-used user IDs just clobbered old accounts with new credentials. Yikes!

Neither of these problems were _caused_ by integer IDs, but in both cases, using a longer, richer user ID would have massively reduced the likelihood that the bugs would have had negative outcomes.

## Switching to UUIDs 👷🏿‍♀️

Let's say you've already gone through the process of generating a new Phoenix app, and you've already followed all the guides from Pow on getting set up, and then you realize that your user IDs are integers. Stop! Don't throw away all of your code and start over. I found myself in the same situation this morning, but thanks to [Cam Stuart on GitHub](https://github.com/danschultzer/pow/issues/502#issuecomment-619377925), I got my IDs switched over in no time. Here are the steps.

### Change the migration. 

```
Open up priv/repo/migrations/<timestamp>_create_users.exs
Change

  def change do
    create table(:users) do
...

  def change do
    create table(:users, primary_key: false) do
...
```

Which tells the migration not to create the default auto-incrementing ID column. Then add a line to create your own ID column:

```
  def change do
    create table(:users, primary_key: false) do
    add :id, :uuid, primary_key: true
...
```

### Change the Model

Next open up `lib/boots/users/user.ex` and add two module attributes above the "schema" declaration:

```
  @primary_key {:id, :binary_id, autogenerate: true} # Add this
  @foreign_key_type :binary_id # And add this
  schema "users" do
    ...
```

Those module attributes tell the schema that it should use UUIDs (here represented as :binary\_id) for the ID column, and that it should auto-generate them. It also tells any other schemas that when they're making a foreign key that references this table, they should also use UUIDs.

### Update the Test

If you've followed the Pow guide on adding API tokens, you'll have a failing test now. Open up `test/your_app_web/api_auth_plug_test.exs` and change the lines that create a test user with an integer ID:

```
    user =
      Repo.insert!(%User{id: 1, email: "test@example.com"})
```

To use a UUID instead:

```
    user =
      Repo.insert!(%User{id: "e2c54c31-e574-4c9f-8672-dad23449d4cf", email: "test@example.com"})
```

### Change your Generators

Now that you've fixed your users, let's make it so that any new models you generate will use UUIDs by default. Open up `config/config.exs` and add the following lines to the end of the file:

```
config :your_app, :generators,
  migration: true,
  binary_id: true,
  sample_binary_id: "11111111-1111-1111-1111-111111111111"
```

🚨 Don't forget to change `:your_app` to the actual name of your app!

This should tell phoenix that whenever it's generating a new model, it should use binary IDs for them.

### All Done!

Congratulations! Your app is now a little more robust 👏. It's worth noting that there has been some discussion about whether using random UUIDs significantly hurts Postgres's performance by causing it to do more random seeks. After my research, the evidence against UUIDs has appeared weak enough to me that I'd rather have the safety than be concerned about possible performance loss. I'd love to hear from you if you have more information on this, though!