Front-End Web & Mobile

NEW: Lazy loading & nested query predicates for AWS Amplify DataStore

Today, we’re announcing three major enhancements to Amplify DataStore to make working with relational data easier: lazy loading, nested query predicates, and type enhancements. DataStore provides frontend app developers the ability to build real-time apps with offline capabilities by storing data on-device (web browser or mobile device), and automatically synchronizing data to the cloud and across devices on an internet connection.

The three new relational data enhancements are:

1. Lazy load related data: You can now asynchronously load related data for hasOne, hasMany, belongsTo, or manyToMany.

  • If a post has many comments, you can lazy load comments using the async toArray() function: await post.comments.toArray().
  • You can also lazy load hasOne relationships, for example: await post.author.
  • DataStore also takes advantage of JavaScript’s built-in async iterator support to make it easier for you to author for loops:
for await (const comment of post.comments) {
  console.log(comment) // iterates over each comment!
}

2. Nested query predicates: You can now query based on conditions of related models.

  • For example, if you only want to get the comments of posts with a title starting with “Amplify”, you can now do the following: await DataStore.query(Comment, c => c.post.title.beginsWith(“Amplify”)

3. Improved types for predicates: Previously predicate operators were passed as strings. The new predicate types allow you to specify the predicate operators as a function call.

  • Before: DataStore.query(Post, p => p.id(‘eq’, ‘123’))
  • Now: DataStore.query(Post, p => p.id.eq(‘123’))

Step 1: Deploy Amplify app with GraphQL API

Let’s walk through all of these enhancements by checking out a blog post demo app:

  • Users can create blog posts, comments, and replies
  • Users can load comments by clicking on the “show comments” button
  • Users can export the blog posts, comments, and replies as a text file
  • Users can filter blog posts based on their comment replies’ content

To get started, we’ll deploy a React app based on an existing sample repository to Amplify Hosting. This allows us to quickly get started with an app backend that contains a GraphQL API backed by DynamoDB. This repository also contains some React components to let users create blog posts, comments, and replies.

Note: To deploy this sample app, you need to have an AWS Account.

One Click Deploy to Amplify Console

As part of this process, Amplify Hosting forks the sample repository into your GitHub account. From there it schedules a new fullstack build and hosts your web app once the build is complete.

While the build is running, you can move on to Step 2 to get your local development environment configured.

Step 2: Clone React app code

Let’s clone the newly forked repository into your local development environment. Go to GitHub to find the newly forked repository under your profile. Follow the instructions on GitHub to clone the repository locally. The terminal command should look something like this:

git clone git@github.com:<YOUR_GITHUB_USERNAME>/amplify-lazy-blog.git

Once the repository is cloned, let’s go into the React project, install the node dependencies, and start the local development server:

cd amplify-lazy-blog
npm install
npm start

Play with the app on http://localhost:3000 to add a few sample blog posts, comments, and replies. This will come in handy later as we flesh out the remaining parts of the app.

Recording of demo app working

Step 3: Build export feature with lazy loading

To show case the new lazy loading functionality, we’ll build an “export” feature that formats all the blog posts, their comments, and replies into a text file. To achieve this, we need to iterate over all posts, their comments, and each comment’s replies.

Go to the App.js file leverage the new for await (...) async iterator syntax to iterate through all the records.

  async function exportAsFile() {
    let output = ""

    for await (const post of posts) {
      output += `${post.title}\n\n`
      output += `${post.content}\n\n`
      output += `Comments:\n`
      for await (const comment of post.comments) {
        output += `- ${comment.content} @ ${comment.createdAt}\n`
        for await (const reply of comment.replies) {
          output += `  - ${reply.content} @ ${reply.createdAt}\n`
        }
      }
      output += `-------\n`
    } 

    downloadString(output)
  }

Your app can now allow users to download all the blog contents in a pre-formatted file.

Recording of export functionality working

Data models that are connected by a “hasMany” relationship provide a toArray() function. This allows you to rewrite the for await (...) loop to the following:

// Using for await (...) loop
for await (const reply of comment.replies) {
    output += `  - ${reply.content} @ ${reply.createdAt}\n`
}

// Using `.toArray()` function 
const replies = await comment.replies.toArray()
output += replies.map(reply => `  - ${reply.content} @ ${reply.createdAt}\n`).join("")

Step 4: Build filter functionality with nested predicates

Next, let’s explore the how to use nested query predicates to apply filters based on conditions of a related data. For example, we’ll build a filter functionality to only show blog posts that have replies with a given keyword.

In the sample code base the filter state is already hooked up to a the <input /> element. All we have to do is edit the useEffect(...) hook to leverage the nested query predicates.

Go to your App.js file and edit the useEffect hook to conditionally apply the filter:

  useEffect(() => {
    let sub;
    if (filter) {
      sub = DataStore
        .observeQuery(Post, p => p.comments.replies.content.contains(filter))
        .subscribe((snapshot) => { // ^ see new type improvements here
          setPosts(snapshot.items)
        })
    } else {
      sub = DataStore
        .observeQuery(Post)
        .subscribe((snapshot) => {
          setPosts(snapshot.items)
        })
    }
    return () => sub.unsubscribe()
  }, [filter])

Now if you go back to your app, you’ll see that once you enter a filter text, you’ll only see blog posts with comment replies that contain the filter text.

Recording of filter functionality working

Step 5: Deploy latest changes

Now that your app is working as expected, you can commit your latest changes to Git and push them to GitHub. Amplify Hosting is going to automatically kick-off a new build and give you a domain to access your app globally.

git commit -a -m "added filter and export feature"
git push -u origin main

🥳 Success

This demo just scratches the surface of what’s possible with AWS Amplify DataStore. Learn more Amplify DataStore at the AWS Amplify documentation.

As always, feel free to reach out to the Amplify team via GitHub or join our Discord community. Follow @AWSAmplify on Twitter to get the latest updates on feature launches, DX enhancements, and other announcements.

Project clean-up

To remove the backend resources generated from this blog post run the following command in your Terminal:

amplify delete --force

Go to GitHub and delete the amplify-lazy-blog repository to remove the frontend artifacts created by this tutorial.