Sorting data in AWSAmplify and DynamoDB with @key directive

Sze Wong
5 min readOct 3, 2019

--

AWSAmplify with AppSync allow us to create web app quickly with built-in support for API (database), Authentication and other basic services that most apps need.

While testing with Amplify, I got stuck on how to get data to return in ORDER. Since DynamoDB is a NOSQL database, it does not support ordered query. Below is how to setup your GraphQL schema so your app can show data in order.

I suppose one simple answer is to scan and load all data to the front-end and sort in browser. However I think that is not practical in the long-run.

All I am looking for is that records will come back in the order that it is inserted. I think that is not too much to ask.

I will skip over all the initial setup and get right into schema.graphql. If you want to learn about how to setup a AWSAmplify project and enable API, please check out here.

type Incident @model{id: IDlocation: Stringtimestamp: AWSTimestampusername: Stringmessage: String}

This is my starting point. A super simple Incident type.

Run [amplify push] so an AppSync instance and DynamoDB table are created.

Next, login to AWSConsole.

In the AppSync query playground, create 4 records.

mutation CreateIncident{
createIncident(input: {timestamp:1570120338,
username:"swong", message:"M1"}){
id
}
}
mutation CreateIncident2{
createIncident(input: {timestamp:1570120477,
username:"swong", message:"M2"}){
id
}
}
mutation CreateIncident3{
createIncident(input: {timestamp:1570120479,
username:"swong", message:"M3"}){
id
}
}
mutation CreateIncident4{
createIncident(input: {timestamp:1570120492,
username:"swong", message:"M4"}){
id
}
}

NOTE: We hardcoded the timestamp so these 4 records are created in sequential order.

Now let’s list them:

query List{
listIncidents {
items {
id,message
}
}
}

The output comes back out of order:

{
"data": {
"listIncidents": {
"items": [
{
"id": "c0655d42-25fe-4c3c-9c2e-63ecac2f0844",
"message": "M4"
},
{
"id": "6aaa5cb1-6912-4800-a3f8-7a60bdf0198c",
"message": "M2"
},
{
"id": "25f6a406-08b2-45c3-940b-5ef718a08030",
"message": "M1"
},
{
"id": "86d45146-b2e7-4f30-96b6-e059b6fbb2ce",
"message": "M3"
}
]
}
}
}

If your dataset is small, its foreseeable that you can list the entire dataset and sort on the front-end. However, that’s not practical in the real world.

To solve this, we have to use Global Secondary Index in DynamoDB and the new @key directive.

Back to schema.graphql and add the @key directive.

type Incident @model@key(name: "ByUser",fields: ["username", "timestamp"],queryField: "incidentsByUser"){id: IDlocation: Stringtimestamp: AWSTimestampusername: Stringmessage: String}

Here we are creating a custom index named: “ByUser”.

It takes 2 fields, username is the partition-key while timestamp is the sort-key. We also tell Amplify to create another query with the name “incidentsByUser” for us.

Run [amplify push] again to update the backend

AppSync will create the Global Secondary Index (GSI) in your DynamoDB. It will go through a backfilling process for any existing data.

Now go back to AppSync Console and try “listIncidents” again

query List{
listIncidents {
items {
id,message
}
}
}
{
"data": {
"listIncidents": {
"items": [
{
"id": "c0655d42-25fe-4c3c-9c2e-63ecac2f0844",
"message": "M4"
},
{
"id": "6aaa5cb1-6912-4800-a3f8-7a60bdf0198c",
"message": "M2"
},
{
"id": "25f6a406-08b2-45c3-940b-5ef718a08030",
"message": "M1"
},
{
"id": "86d45146-b2e7-4f30-96b6-e059b6fbb2ce",
"message": "M3"
}
]
}
}
}

Oh no. The result is still out of order.

Open up the Schema section on the right and you will notice a new Query called: “incidentsByUser”. We want to use this query instead of the “listIncidents” query.

query ListByUser{
incidentsByUser(username:"swong"){
items {
id,message
}
}
}
{
"data": {
"incidentsByUser": {
"items": [
{
"id": "25f6a406-08b2-45c3-940b-5ef718a08030",
"message": "M1"
},
{
"id": "6aaa5cb1-6912-4800-a3f8-7a60bdf0198c",
"message": "M2"
},
{
"id": "86d45146-b2e7-4f30-96b6-e059b6fbb2ce",
"message": "M3"
},
{
"id": "c0655d42-25fe-4c3c-9c2e-63ecac2f0844",
"message": "M4"
}
]
}
}
}

Volia, data comes back in order!

Now most likely you will have multiple users, what if you have an Admin screen and want data to come back in order for all users.

In order to do that you will want to put all the records in a new common partition.

We will create the concept of tenant where all users of the same customer will live.

Again back to schema.graphql

type Incident @model@key(name:"ByUser", fields:["username","timestamp"],queryField:"incidentsByUser")@key(name:"ByTenant", fields["tenant","timestamp"], queryField:"incidentsByTenant"){id: IDtenant: Stringlocation: Stringtimestamp: AWSTimestampusername: Stringmessage: String}

Run [amplify push] again.

Go back to AppSync Console. We want to add the tenant field to all the records:

mutation Update{
updateIncident(input: {id: "25f6a406-08b2-45c3-940b-5ef718a08030", tenant:"customer1"}){
id
}
}
mutation Update2{
updateIncident(input: {id: "6aaa5cb1-6912-4800-a3f8-7a60bdf0198c", tenant:"customer1"}){
id
}
}
mutation Update3{
updateIncident(input: {id: "86d45146-b2e7-4f30-96b6-e059b6fbb2ce", tenant:"customer1"}){
id
}
}
mutation Update4{
updateIncident(input: {id: "c0655d42-25fe-4c3c-9c2e-63ecac2f0844", tenant:"customer1"}){
id
}
}
mutation CreateIncident5{
createIncident(input: {timestamp:1570120476,
username:"swong2", message:"M1.1", tenant:"customer1"}){
id
}
}

Notice that we added a fifth message with a different username but the timestamp sits between M1 and M2.

Now run the following query

query ListByTenant{
incidentsByTenant(tenant:"customer1"){
items {
id,message
}
}
}
{
"data": {
"incidentsByTenant": {
"items": [
{
"id": "25f6a406-08b2-45c3-940b-5ef718a08030",
"message": "M1"
},
{
"id": "3addf4c3-ee17-45d8-a71b-a64d2b72cdd9",
"message": "M1.1"
},
{
"id": "6aaa5cb1-6912-4800-a3f8-7a60bdf0198c",
"message": "M2"
},
{
"id": "86d45146-b2e7-4f30-96b6-e059b6fbb2ce",
"message": "M3"
},
{
"id": "c0655d42-25fe-4c3c-9c2e-63ecac2f0844",
"message": "M4"
}
]
}
}
}

And records come back in order across all users.

--

--