How to use custom identifiers with the API Platform

When developing Web APIs, you often need to think about your resource identifiers.

Resource identifier can be anything from a simple incrementing integer to more complex UUID fields. The only real constraint is that the field is unique.

By default, API Platform 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 use custom identifiers?

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 a 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 give up any information about a 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, you just have to ensure that it is unique. The primary advantage of a 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 front application without using slug as your resource id, you will have to resort to filtering.

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

How to use custom identifiers?

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 for UUID generation.

<?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 a slug, you can use the Gedmo Sluggable Doctrine extension.

<?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.

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

And that’s the gist… that’s how you can use custom identifiers with the API Platform.