Front-End Web & Mobile

Build a user settings store with AWS AppSync

Amazon Cognito Sync is a service that you can use for syncing application-related user profile data across devices. The client library caches the data locally so that the app can read and write data, regardless of the device connectivity state. The data stored is limited to key-value pairs where the keys and values are both strings.

However, many organizations need to store more flexible data types—such as integers, JSON documents, and images—in a common store. For example, you might want to store binary data like profile pictures, store the preferences in an alternate data source, or do backend searches that enable you to segment users and do A/B testing.

In this article, I’ll create a data store for storing user settings by using AWS AppSync, which provides a similar structure as Amazon Cognito Sync, but has some additional functionality. AWS AppSync is a GraphQL-based managed service with real-time and offline capabilities. A benefit of this type of data store is that it allows you to extend the store for additional data types. We’ll use this data store in future articles (iOS, Android) for storing user details in an offline repository on Android and iOS devices.

Step 1: Decide on the data model

There are three different ways of setting up user settings, and these are modeled using a GraphQL type.  First, I can decide on the user settings up front, define a type of these user settings, and then store a single record per user in the data store.  In a GraphQL schema, this would look like the following:

type Settings {
  theme: String
  displayName: String
  signature: String
  requestAlert: Boolean
}

type Query {
  getSettings: Settings!
}

type Mutation {
  storeSettings(settings: Settings!): Settings!
}

This has the advantage that you’ve specified which settings are permissible. I can store or retrieve any individual setting when it’s required. The schema can also handle nested binary objects (using complex objects) and different types (like dates, numbers, and Booleans).

However, GraphQL is a strongly typed system. This means that I don’t have to use all of the fields in a type on a client, but I can’t use additional fields without adding them into the schema within the service first.

I can also store each preference as a key-value pair:

type Setting {
  key: String!
  value: String!
}

type Query {
  getSettings: [Setting]
  getSetting(key: String!): String!
}

type Mutation {
  storeSetting(key: String!, value: String!): Setting
}

This allows me to use the type system of GraphQL with more flexibility on the client. However, it has the disadvantage of only being able to store strings. Most data has to be converted in order to be stored, and binary data is problematic.

The final technique is to serialize the settings and store the settings as a string:

type Query {
  getSettings: String!
}

type Mutation {
  storeSettings(json: String!): String!
}

In this case, I can store any serialized data, as long as it fits in the size of a string within the data store after the data is serialized. For Amazon DynamoDB, that’s limited to 400 KB. This technique has even less flexibility than the key-value store—mostly because the data is stored as a large blob with a specific format.

In this article, I’m going to use the first technique, as it provides great flexibility when I need to work with binary objects.

Step 2: Set up Amazon Cognito

There are two authentication techniques for storing user data—IAM and user pools. Amazon Cognito Sync uses an identity pool for authentication (similar to the IAM role), so I’ll do the same. However, the same technique can be used for user pools. The configuration of AWS AppSync is slightly different in that case.

To easily set up Amazon Cognito, use AWS Mobile Hub:

  1. Open the AWS Mobile Hub console.
  2. To create a new project, choose Create.
  3. Type a suitable name for the project, and then choose Next.
  4. Choose Cancel to move directly to your project.
  5. Scroll down to the Add More Backend Features section.
  6. Choose the + under the User Sign-in card.
  7. Choose Email and Password.
  8. Choose Create user pool.

This process sets up a user pool and identity pool for Amazon Cognito. You can also integrate Facebook or Google social providers, or an enterprise identity provider (IdP) like Active Directory using SAML.

If you already have an Amazon Cognito user pool and identity pool, then you can use the existing resources. You can also configure these resources within the Amazon Cognito console.

Step 3: Set up DynamoDB

In this example, I use DynamoDB as my store.  You can configure DynamoDB to work with Amazon Cognito easily within AWS Mobile Hub.

  1. Return to your project in Mobile Hub (if you’re following the prior section, choose the blue banner at the top).
  2. Scroll down to the Add More Backend Features section.
  3. Choose the + under the NoSQL Database section.
  4. Choose Enable NoSQL, and then choose Add Table.
  5. Choose Custom, and then fill in the table form:
    • Table name: settings
    • Permission: Private
  6. Choose Create table, and then choose Create table in the pop-up confirmation dialog box.

It takes a couple of moments for the data table to be created.  After that’s complete, you can move onto the next step.

Step 4: Set up AWS AppSync

Configuring AWS AppSync is a multi-step process, but I’ve done all the prerequisites.  Let’s create the API:

  1. Open the AWS AppSync console.
  2. Choose Create API.
  3. Type a name for your API, choose Custom schema, and then choose Create.
  4. Choose Data Sources in the left-hand menu.
  5. Choose New.
  6. Fill in the form:
    • Data source name: settingsTable
    • Data source type: Amazon DynamoDB table
    • Region: <choose your region>
    • Table Name: settings
    • Create or use an existing role: New role
  7. Choose Create.
  8. In the left-hand menu, choose Settings.
  9. Under Authorization type, choose AWS Identity and Access Management (IAM).
  10. Choose Save.
  11. In the left-hand menu, choose Schema.
  12. Copy and paste the following code into the Schema window:
type Settings {
  theme: String
  displayName:String
}

input SettingsInput {
  theme: String
  displayName: String
}

type Query {
  getSettings: Settings
}

type Mutation {
  storeSettings(settings:SettingsInput):Settings
}

schema {
  query: Query
  mutation: Mutation
}
  1. Choose Save.

Note that the Settings and SettingsInput types are the same. GraphQL allows for the return type to be different than the type that’s accepted for storage. In this example, they are the same type.

The final step in creating the API is to link the queries and mutations (we have one of each) to the data store by using a resolver. We also want the identity of the user to be automatically stored. This is so that the user always retrieves and stores their settings, but can’t see anyone else’s settings:

  1. Still in the Schema menu, type Query in the Filter types search box.
  2. Choose Attach next to getSettings.
  3. Fill in the form:
    • Data source name: settingsTable
    • Request Mapping: copy and paste the following:
      {
        "version": "2017-02-28",
        "operation": "GetItem",
        "key": {
          "userId": { "S": "${ctx.identity.cognitoIdentityId}" }
        }
      }
      				
    • Response Mapping: copy and paste the following:
      $util.toJson($ctx.result.settings)
  4. Choose Save.
  5. In the left-hand menu, choose Schema, and then type Mutation in the Filter types search box.
  6. Choose Attach next to storeSettings.
  7. Fill in the form:
    • Data source name: settingsTable
    • Request Mapping: copy and paste the following:
      {
          "version" : "2017-02-28",
          "operation" : "PutItem",
          "key" : {
              "userId" : { "S" : "${ctx.identity.cognitoIdentityId}" }
          },
          "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.settings)
      }
      				
    • Response Mapping: copy and paste the following:
      $util.toJson($ctx.result.settings)
  8. Choose Save.

Step 5: Provide a policy in the OAM roles

The final step is to include a policy in the IAM roles to allow your users to access the API. For this, you need two pieces of information:

  • The IAM role name
  • The AWS AppSync API ID

To find the IAM role name:

  1. Open the AWS Mobile Hub console.
  2. Choose your project.
  3. In the top-right corner, choose Resources.
  4. Find the AWS Identity and Access Management Roles panel.

The IAM role for the authenticated web role is listed with the string _auth_MOBILEHUB:

Because it’s been created with AWS Mobile Hub, the IAM role is based on your project name.

To find the AWS AppSync API ID, open the AWS AppSync console. The API ID is listed directly below the API name:

To install the IAM policy:

  1. Open the IAM console.
  2. Find the Authenticated role within the list (use the search box).
  3. Choose Add inline policy.
  4. Choose the JSON tab.
  5. Copy and paste the following in the box provided.
    {
       "Version": "2012-10-17",
       "Statement": [
          {
             "Effect": "Allow",
             "Action": [
                "appsync:GraphQL"
             ],
             "Resource": [
                "arn:aws:appsync:{REGION}:{ACCOUNTID}:apis/{APIID}/*"
             ]
          }
        ]
    }
    		

    Replace the following:

    • {REGION} is the region that your AWS AppSync API is running in.
    • {ACCOUNTID} is your AWS account ID. It’s the 12-digit number at the top of the screen in the banner. Remove the hyphens.
    • {APIID} is the AWS AppSync API ID.
  6. Choose Review policy.
  7. Type a suitable name for the policy, and then choose Create policy.

Optional: If you don’t have authenticated users, you can repeat this process for the unauth_MOBILEHUB IAM role to allow users to sync settings. However, the idendityId can change and doesn’t sync across devices because these unauthenticated users are considered device authenticated.

Your API is now fully configured.

Step 6: Integrate into your client

We have additional articles for both iOS and Android integrations.  See the appropriate article for your platform: