Custom identifiers with ApiPlatform
Tags: php, symfony, apiplatform, engineeringmonday
Published: 2020-07-20 12:00:56

When developing Web APIs, you often need to think about your resource identifiers. Resource identifier can be anything from simple incrementing integer to more complex uuid fields. The only real constraint is that the field is unique.

By default, ApiPlatform is going to use the field named `id` for your identifier. If you are using Doctrine as your ORM, that field will usually be a simple integer field with auto-increment option. 

#Why?
There are a number of reasons why you would want to use something else as a resource identifier for your API. If your API is publicly available, exposing the internal database id might also expose the information you don't want everyone to know. For example, knowing that your purchase has `id = 17` might tell you that this was only the 17th purchase on that webpage. Also, you might be able to iterate over that id and see the order that resources were created. Or even worse, find some resources you are not supposed to find (although, that usually indicates you have a different sort of security problem).

Another information you are exposing is *data velocity*. For example, by comparing the IDs of different Facebook posts you might see how many new posts Facebook has every day. To summarise, these are the main problems:

1. Data count
2. Data iteration
3. Data velocity

Notice that we can solve the first problem by offsetting the starting id number (by setting it to something like `13000`), but the other problems are not that easy to solve.

The two commonly used API ids are uuid and slug. With uuid we get a random generated string that doesn't really expose any information about resource. Another advantage we get with uuid is that it can be client generated. This can help when creating relations before the resources are even stored in the database.

Slug can also be used as a resource id, we just have to ensure that it is unique. The primary advantage of slug is that it "looks good" in the url, so it can be used to generate pretty links. If you want to get pretty urls in your frontend application without using slug as your resource id, you will have to resort to filtering.

If you are using relational database, you will probably want to keep your primary key as an incrementing integer. There are performance benefits of having easily iterable field as your primary id, especially if you have a well normalized database structure.

#How?
To use a different identifier on an Api Platform resource, you need simply need to tell Api Platform which field to use for id. You can use [ramsey/uuid](https://github.com/ramsey/uuid) for uuid generation.	

```php
<?php

namespace App\Entity;

use Ramsey\Uuid\Uuid;

/**
 * @ORM\Entity
 * @ApiResource
 */
class Product
{

    public function __construct()
    {
        $this->uuid = Uuid::uuid4()->toString();
    }
    /**
     * @var int|null
     * @ApiProperty(identifier=false)
     *
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
    
    /**
     * @var string
     * @ApiProperty(identifier=true)
     * @ORM\Column(type="uuid")
     */
    public $uuid;
}

```
If you want to use slug, you can use [Gedmo Sluggable](https://github.com/Atlantic18/DoctrineExtensions) Doctrine extension.

```php
<?php

namespace App\Entity;

use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ORM\Entity
 * @ApiResource
 */
class Product
{

    /**
     * @var int|null
     * @ApiProperty(identifier=false)
     *
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var string
     * @Gedmo\Slug(fields={"name"})
     * @ApiProperty(identifier=true)
     * @ORM\Column(type="string", length=128, unique=true)
     */
    private $slug;
}
```

Also, you need to add this configuration to `services.yaml` to enable the slug generation before inserting it to database.


```yaml
    gedmo.listener.sluggable:
        class: Gedmo\Sluggable\SluggableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ "@annotation_reader" ] ]
```

And that's all there is to it. Api Platform magically does the rest. We can still keep using internal id for internal stuff, and serve our api consumers with external id.

What do you use as identifiers in your applications? And why?