The AWS Amplify GraphQL Transform toolchain exposes the @key directive which lets you define custom index structures. In other words, you can sort the data in your queries with it. This is useful if you have queries that you want to sort on the server side instead of the client side. We will look at an example with pagination.

DynamoDB

To understand the @key directive, you need to know how DynamoDB saves data - specifically primary keys and secondary indexes 🧐. Like other databases, DynamoDB stores data in tables. The docs give an excellent summary.

A table is a collection of items, and each item is a collection of attributes. DynamoDB uses primary keys to uniquely identify each item in a table and secondary indexes to provide more querying flexibility.”

Other than the primary key, tables are schemaless and different items in the same table can have different attributes.

Each attribute can either be a scalar or a nested attribute. A scalar is a primitive value such as a string or a number. An attribute is nested, if it has attributes itself. E.g. if a Song has a Genre , which has a Name , which is a string, Genre would be a nested attribute.

In the example above, SongId is the primary key which uniquely identifies the item in the table.

But you could also leave it out, if you choose to identify the song by Title and Artist and these two attributes together make up the primary key.

The downside is that using Artist and Title there couldn't be two songs from the same artist with the same title.

Primary keys 🥇

When you create a table in DynamoDB, you have to specify how you want to identify the items in the table.

If you use a single attribute like SongId , it is called a partition key. The name originates from the fact that the partition key is used in an internal hash function to determine the physical storage internal to DynamoDB by evenly distributing data items across partitions. Therefore, the partition key is sometimes referred to as the primary index's hash key. Now it makes sense that the primary key has to be unique, doesn't it?

If you use two attributes like Artist and Title , you are using composite primary keys. The first attribute is the partition key, and the second attribute is the sort key. Just like with one key, the partition key determines the physical location. Using composite primary keys, items with the same partition key are stored together, but they can be distinguished because they are sorted in order by the sort key. Sort keys are sometimes referred to as the range key.

Secondary Indexes 🥈

Optionally, you can add secondary indexes to your tables, which let you do queries against other attributes in addition to the primary key. The table which the secondary index is associated with is called the base table.

There are two types of secondary indexes: global and local. Global secondary indexes have both a different partition key and a different sort key from the base table, while secondary indexes have the same partition key, but a different sort key. Their naming comes from the fact that global indexes can query data across all partitions, whereas local indexes are scoped to it’s partition key. It follows that local secondary indexes must always be composite.

In conclusion, using secondary indexes, you could also query the songs by Key even though its not part of the primary index.

Note that the attributes for composite keys for both primary keys and secondary indexes must always be top-level attributes of type string, number, or binary.

@key

We have the basics down. Let’s examine AWS Amplify’s @key directive. You can add @key to @model directives. Each @model generates a table. Using @key , we can either overwrite its primary key or add secondary indexes. The former you can only do once for each @model , while DynamoDB's limits limit the ladder.

We are going to look at a contact list example. Let’s define our model without the @key directive.

Now we can query for distinct users by using the getContact query, where id is the respective contact's UUID.

Note that in this example we assume id to be defined and pass it to graphqlOperation using the object shorthand notation. The listContacts query can be used like this without any additional arguments for each schema that we'll define below. What changes is the behaviour of the get queries and the list queries with arguments.

Behind the scenes, AWS Amplify created a table for the contact scheme where id is the partition key, which is why we can provide it as an argument to the getContact query.