Working with different IDs through the application
Tags: graphql, database, frontend
Published: 2024-01-16 17:01:54

An entity can have different IDs through the application, from the database to the frontend. For example, a user can have an ID in the database, and another ID in the frontend because of the [GraphQL Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm) that Relay specifies. And that is because of the concept of a node in a [cursor-based pagination](https://graphql.org/learn/pagination/) that we need to have these different IDs.

The concept of a node in a GraphQL API is that you can retrieve any data from the database just by knowing the global id of the entity. For example, if you want to retrieve the user with ID `1`, you can do it with the following query:

```graphql
query {
  node(id: "VXNlcjox") {
    ... on User {
      id
      name
    }
  }
}
```

The global ID is composed of the type of the entity and the ID of the entity. In this case, the type is `User` and the id is `1`. The global id is encoded in base64, so the global id is `VXNlcjox` (`base64("User:1")`). The same could be for a post, for example, the global id of the post with id 1 is `UG9zdDox` (`base64("Post:1")`). So these entities have the same ID in the database, but different IDs in the frontend.

## Many ids

Given this brief introduction, let's see how to work with these different IDs in the application.

### Database id

In the database, we can use the many formats that we want for the IDs. For example, we can use the `uuid` type for the ids, or we can use the `int` type for the ids. It depends on the database that we are using and the requirements of the application. Some databases, such as DynamoDB may require two fields for the IDs, one for [the partition key and another for the sort key](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html).

As some databases can repeat the IDs, the ID is not enough to identify an entity through the application.

### Record id

The record ID is the ID that the database uses to identify the entity. For example, if we are using a relational database, the record ID is the primary key of the table. If we are using DynamoDB, the record ID is the partition key and the sort key.

For example, if the entity User is on a PostgreSQL database, the record ID is the primary key of the table.

```typescript
const getRecordId = (itemFromPostgreSQL) => {
  return itemFromPostgreSQL.id;
};
```

If the entity User is on a DynamoDB database, the record ID is the partition and sort keys.

```typescript
const getRecordId = (itemFromDynamoDB) => {
  return [itemFromDynamoDB.partitionKey, itemFromDynamoDB.sortKey].join(':');
};
```

### Global id

The record ID is not enough to identify an entity through the application. We need to use the global ID to identify an entity through the application. The global ID is composed of the type of the entity and the record ID of the entity. For example, if the entity User is on a PostgreSQL database, the global id is `VXNlcjox` (`base64("User:1")`). If the entity User is on a DynamoDB database, the global id is `VXNlcjpVU0VSOjE=` (`base64("User:USER:1")`), in which the partition key is `USER` and the sort key is `1`.

```typescript
const getGlobalId = (itemFromPostgreSQL) => {
  return encode(`User:${itemFromPostgreSQL.id}`);
};

// or

const getGlobalId = (itemFromDynamoDB) => {
  return encode(
    `User:${itemFromDynamoDB.partitionKey}:${itemFromDynamoDB.sortKey}`
  );
};
```

Now, we can use the global ID to identify an entity through the application, even if the application has many entities with the same database ID.

## Considerations

To avoid complexity on the frontend, we can use the global ID as the ID of the entity on the frontend. It doesn't need to know that some entity has many IDs through the application.

You should also be careful with the separators that you use to compose the [global id](#global-id) and the [record id](#record-id). The examples of this article use `:` as the separator for both, but you can use any separator that you want. Just be careful to not use a separator that can be used in the IDs of the entities. For example, if you use `:` as the separator, you can't use `:` in the ids of the entities.

Also, you have to respect the order from the [global id](#global-id) and the [database id](#database-id). When you split the decoded global ID, the first part is the type of the entity and the following part is an array of the record ID of the entity if you use the same separator for both. For example, if you use `:` as the separator, the global id `VXNlcjpVU0VSOjE=` (`base64("User:USER:1")`) is split to `["User", "USER", "1"]`. The first part is the type of the entity and the following part is an array of the record ID of the entity.

```typescript
const getRecordId = (globalId) => {
  const [type, ...recordId] = decode(globalId).split(':');

  return recordId.join(':');
};
```

## @ttoss/ids

To help you with the IDs, we created a package called `@ttoss/ids`. It has some functions to help you with the ids. You can see the documentation of the package [here](/docs/modules/packages/ids/).