loading...
Cover image for Custom identifiers with ApiPlatform
Bornfight

Custom identifiers with ApiPlatform

markopaden profile image Marko Pađen Originally published at bornfight.com ・3 min read

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 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 slug, you can use 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 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?

Posted on by:

markopaden profile

Marko Pađen

@markopaden

Backend Developer PHP

Bornfight

Digital Innovation Company that creates custom software, digital products, mobile apps, websites and interactive solutions.

Discussion

markdown guide
 

This is cool. Unless you use something else than mysql. Because it fits better to my needs, I use MongoDB and I have to use classic auto-indent Id. I can't use custom uuid property.

 

This is indeed written with relational databases in mind. However, you still want to hide internal incremental ID from your API for the reasons mentioned above.

 

In Laravel, I use timestamp-first UUID4 to make the database indexing and orderby works just like auto-incremented IDs.

 

Depending on your use case, exposing timestamp (as date of creation) can be even worse than exposing incremental id. Of course, if your domain requirements are different, it is a perfectly good solution.