DEV Community

loading...
AWS Community Builders

Modern Full-Stack Serverless, Part VII

Salah Elhossiny
ML engineer || AWS Certified MLS || AWS Community Builders member || Fullstack developer
・6 min read

Amplify supports another type of API for interacting with AppSync: Amplify DataStore. DataStore has a different approach than a traditional GraphQL API.

Instead of interacting with the GraphQL API itself, using queries and mutations, DataStore introduces a client-side SDK that allows you to write to and read from a local store and persists this data locally using the local storage engine of the platform you are working with (i.e., IndexDB for web and SQLite for native iOS and Android).

DataStore then automatically syncs the local data to the GraphQL backend for you as updates are made both locally and remotely.

Using the DataStore SDK, you then only have to perform the
operations like save, update, and delete, writing directly to DataStore
itself. DataStore handles everything else for you: it syncs your data to
the cloud when you have an internet connection, and if you’re not
online, will queue it for the next time you’re connected.

About Amplify DataStore

Amplify DataStore is a combination of the following things:

AppSync GraphQL API.

Local storage repository and syncing engine that also persists data offline.

Client-side SDK for interacting with the local storage repository.

Special sync-enabled GraphQL resolvers (generated by the Amplify CLI) that enable sophisticated conflict detection and conflict resolution on the server.

Amplify DataStore Overview

When getting started with DataStore, you still create the API as we have done in past chapters. The main difference is, when creating the
API, you will enable conflict detection in the advanced settings of the CLI flow.

From there, to enable DataStore on the client, we need to create
models for DataStore to use to interact with the storage repository.

This can easily be done by just using the GraphQL schema you
already have and running a build command—amplify codegen
models—from the CLI.

Now, you are all set up and can begin interacting with DataStore.

Amplify DataStore Operations

To interact with the Store, first import the DataStore API from Amplify and the Model you’d like to use. From there, you can perform actions against the store.

Operations & Commands

Import the model and DataStore
API


  import { DataStore } from '@aws-amplify/datastore';
  import { Message} from './models'; 

Enter fullscreen mode Exit fullscreen mode

Saving data

await DataStore.save(
  new Message({
    title: 'Hello World',
    sender: 'Chris'
  })
))

Enter fullscreen mode Exit fullscreen mode

Reading data

const posts = await DataStore.query(Post); 
Enter fullscreen mode Exit fullscreen mode

Updating data


const message = await DataStore.query(Message, '123')

await DataStore.save(
  Post.copyOf(message, updated => { updated.title = 'My new title'})
)

Enter fullscreen mode Exit fullscreen mode

Observing/subscribing to changes in data for real-time functionality


 const subscription = DataStore.observe(Message).subscribe(msg => {
    console.log(message.model, message.opType, message.element)
});
Enter fullscreen mode Exit fullscreen mode

DataStore Predicates
You can apply predicate filters against the DataStore using the fields
defined on your GraphQL type along with the following conditions
supported by DynamoDB:

Strings: eq | ne | le | lt | ge | gt | contains | notContains | beginsWith | between

Numbers: eq | ne | le | lt | ge | gt | between

Lists: contains | notContains

Enter fullscreen mode Exit fullscreen mode

For example, if you wanted a list of all messages that have a title
that includes “Hello”:


const messages = await DataStore.query(Message, m => m.title('contains', 'Hello'))

Enter fullscreen mode Exit fullscreen mode

You can also chain multiple predicates into a single operation:

const message = await DataStore .query(Message, m => m.title('contains', 'Hello').sender('eq', 'Chris'))
Enter fullscreen mode Exit fullscreen mode

These predicates enable you to have many ways to retrieve different selection sets from your local data. Instead of retrieving the entire collection and filtering on the client, you are able to query from the store exactly the data that you need.

Building an Offline and Real-Time App with Amplify DataStore

Users of the app can create a new message and all other users will
receive the message in real time. If a user goes offline, they will
continue to be able to create messages. Once they are online, the
messages will be synced with the backend, and all other messages
created by other users will also be fetched and synced locally.
Our app will perform three types of operations against the DataStore
API:

save

Creating a new item in the DataStore; saves the item locally and
performs a GraphQL mutation behind the scenes.

query

Reading from the DataStore; returns a single item or list (array)
and performs a GraphQL query behind the scenes.

observe

Listening for changes (create, update, delete) in data and performs
a GraphQL subscription behind the scenes.

Creating the Base Project

To get started, we will create a new React project, initialize an
Amplify app, and install the dependencies.
The first thing we will do is create the React project:

  ~ npx create-react-app rtmessageboard
  ~ cd rtmessageboard
Enter fullscreen mode Exit fullscreen mode

Next, we will install the local dependencies.

Amplify supports a full installation of Amplify, and scoped (modular)
installations for specific APIs. Scoped packages reduce the bundle
size, since we’re installing only the code that we are using. Since we
are only using the DataStore API, we can install the scoped DataStore
package.

We will also install Ant Design (antd) for styling, React Color
(react-color) for an easy-to-use color picker, and the scoped
dependency for Amplify Core in order to still configure the Amplify
app with aws-exports.js:

  ~ npm install @aws-amplify/core @aws-amplify/datastore antd react-color
Enter fullscreen mode Exit fullscreen mode

Next, initialize a new Amplify project:

  ~ amplify init

  # Follow the steps to give the project a name, environment name, and set the default text editor.
  # Accept defaults for everything else and choose your AWS Profile
Enter fullscreen mode Exit fullscreen mode

Creating the API

Now we will create the AppSync GraphQL API:

  ~ amplify add api

  ? Please select from one of the below mentioned services:GraphQL
  ? Provide API name: rtmessageboard
  ? Choose the default authorization type for the API: API key
  ? Enter a description for the API key: public
  ? After how many days from now the API key should expire (1-
  365): 365 (or your
  preferred expiration)
  ? Do you want to configure advanced settings for the GraphQL
  API: Yes
  ? Configure additional auth types: N
  ? Configure conflict detection: Y
  ? Select the default resolution strategy: Auto Merge
  ? Do you have an annotated GraphQL schema: N
  ? Do you want a guided schema creation: Y
  ? What best describes your project: Single object with
  fields
  ? Do you want to edit the schema now: Y
Enter fullscreen mode Exit fullscreen mode

Update the schema with the following type:

type Message @model {
id: ID!
title: String!
color: String
image: String
createdAt: String
}
Enter fullscreen mode Exit fullscreen mode

Now that we have created the GraphQL API, and we have a GraphQL schema to work with, we can create the models we’ll need for working the local DataStore API (based on the GraphQL schema):

  ~ amplify codegen models
Enter fullscreen mode Exit fullscreen mode

This will create a new folder in our project called models. Using the models in this folder, we can start interacting with the DataStore API. Deploy the API:

  ~ amplify push --y
Enter fullscreen mode Exit fullscreen mode

Writing the Client-Side Code

First, open src/index.js and configure the Amplify app by adding the following code below the last import:


  import 'antd/dist/antd.css'
  import Amplify from '@aws-amplify/core'
  import config from './aws-exports'

  Amplify.configure(config)
Enter fullscreen mode Exit fullscreen mode

Next, open App.js and update it with the following code:


/* src/App.js */

import React, { useState, useEffect } from 'react'
import { SketchPicker } from 'react-color'
import { Input, Button } from 'antd'

import { DataStore } from '@aws-amplify/datastore'
import { Message} from './models'

const initialState = { color: '#000000', title: '' }

function App() {
  const [formState, updateFormState] = useState(initialState)
  const [messages, updateMessages] = useState([])
  const [showPicker, updateShowPicker] = useState(false)

  useEffect(() => {
    fetchMessages()

    const subscription = DataStore.observe(Message).subscribe(() => fetchMessages())

    return () => subscription.unsubscribe()
   }, [])

  async function fetchMessages() {

    const messages = await DataStore.query(Message)

    updateMessages(messages)
  }

  function onChange(e) {
    if (e.hex) {
      updateFormState({ ...formState, color: e.hex})
    } 
    else { 
      updateFormState({ ...formState, [e.target.name]: e.target.value}) 
    }
  }

  async function createMessage() {
    if (!formState.title) return
      await DataStore.save(new Message({ ...formState }))
      updateFormState(initialState)
  }

  return (
    <div style={container}>
    <h1 style={heading}>Real Time Message Board</h1>
    <Input
    onChange={onChange}
    name="title"
    placeholder="Message title"
    value={formState.title}
    style={input}
    />
    <div>
    <Button
    onClick={() => updateShowPicker(!showPicker)}
    style={button}
    >Toggle Color Picker</Button>
    <p>Color:
    <span
    style={{fontWeight: 'bold', color:
    formState.color}}>{formState.color}
    </span>
    </p>
    </div>
    {
    showPicker && (
    <SketchPicker
    color={formState.color}
    onChange={onChange} /
    >
    )
    }

    <Button type="primary" onClick={createMessage}>Create Message</Button>

    {
    messages.map(message => (
    <div
    key={message.id}
    style={{...messageStyle, backgroundColor:message.color}}
    >
    <div style={messageBg}>
    <p style={messageTitle}>{message.title}</p>
    </div>
    </div>
    ))
    }
    </div>
  );
}

const container = { width: '100%', padding: 40, maxWidth: 900 }

const input = { marginBottom: 10 }
const button = { marginBottom: 10 }

const heading = { fontWeight: 'normal', fontSize: 40 }
const messageBg = { backgroundColor: 'white' }

const messageStyle = { padding: '20px', marginTop: 7, borderRadius: 4 }
const messageTitle = { margin: 0, padding: 9, fontSize: 20}

export default App;

Enter fullscreen mode Exit fullscreen mode

Conclusion

Here are a few things to keep in mind from this chapter:

  1. Amplify enables two different APIs to interact with AppSync: the API category as well as DataStore.

  2. When using DataStore, you are no longer sending HTTP requests directly to the API. Instead, you are writing to the local storage engine, and DataStore then takes care of syncing to and from the cloud.

  3. Amplify DataStore works offline by default.

References:

Notes from Book: Full Stack Serverless: Modern Application Development with React, AWS, and GraphQL
By: Nader Dabit
Part 1 : https://dev.to/salah856/modern-full-stack-serverless-part-i-34cb

Part 2 : https://dev.to/aws-builders/modern-full-stack-serverless-part-ii-94i

Part 3 : https://dev.to/aws-builders/modern-full-stack-serverless-part-iii-ha

Part 4 : https://dev.to/aws-builders/modern-full-stack-serverless-part-iv-54bl

Part 5: https://dev.to/aws-builders/modern-full-stack-serverless-part-v-35j6

Part 6: https://dev.to/aws-builders/modern-full-stack-serverless-part-vi-25e3

Discussion (0)

Forem Open with the Forem app