Creating An Amplify App With Persistent Data

This is my developer journal as I learn about creating an Amplify app with persistent data. The app will be built with JavaScript app with persistent data storage on AWS Amplify. I am using Webstorm as my IDE, git as my version control system. Node will be one of the main libraries with NPM as the package manager.

Local Environment

  • Create a new NodeJS project in Webstorm.
    • Add a folder (project) using lowercase directory names. (I named mine alpha-stocker.)
    • Use the default path for node as the engine (18.2) and NPM (9.2).
Webstorm NodeJS project initialization.

When the project opens, go to the terminal window.

Install Amplify CLI (globally) – today this command installed version 10.6.2.

 npm install -g @aws-amplify/cli

Create a react app as a subcomponent that will manage the datastore. Initialize the amplify app using the node script.

npx create-react-app amplify-datastore --use-npm
cd amplify-datastore
npx amplify-app@latest
Creating an amplify app with persistent data
Install Amplify CLI and create the amplify-data-store subproject.

This will create a subfolder named amplify-datastore that will contain a react + amplify app.

Note: My guess is that for this stage of the app, that subdirectory will be the “entire app”; Or at least the entire first go at a persistent data storage app. It very well could be one of many microservices for a larger platform.

In the image below, the subdirectory is show as well as the “overall platform” package.json. This could possibly be where full platform deployments or cross-service “glue” would live. I anticipate this will be largely unused for now.

The top level “platform” folder structure for Alpha Stocker

You can see amplify-datastore has an entire Node app within from the following screen capture.

The amplify-datastore Node app.

Related Amplify CLI Datastore commands suggested on install include:

Some next steps:
"npm run amplify-modelgen" will allow you to generate models/entities for your GraphQL models
"npm run amplify-push" will build all your local backend resources and provision them in the cloud

Defining The Data Model

Running the above commands provides a temporary data model that can be used to do a test run on the Amplify Datastore service.

The model definition is in the amplify-datastore/amplify/backend/api/amplifyDatasource folder in a file named schema.graphql.

The data model path.

The template GraphQL data schema that is provided looks like this:

type Task @model {
  id: ID!
  title: String!
  description: String
  status: String
}
type Note @model {
  id: ID!
  content: String!
}

Revise it to look like this:

type Message @model {
    id: ID!
    title: String!
    color: String
    createdAt: String
}

Building The Model

If you follow the Amazon Getting Started guide you’ll note they suddenly change to Yarn for node management and get Ant Design involved as their preferred UX framework for the React app.

Amplify Getting Started Vid at https://youtu.be/wH-UnQy1ltM

Tim to install some supporting Node libraries and frameworks. Let’s do that and run the model generator.

npm install antd react-color @aws-amplify/core @aws-amplify/datastore
npm run amplify-modelgen

This threw out some interesting warnings we may have to deal with later:

> amplify-datastore@0.1.0 amplify-modelgen
> node amplify/scripts/amplify-modelgen.js

Running codegen...

⚠️  WARNING: Some types do not have authorization rules configured. That means all create, read, update, and delete operations are denied on these types:
         - Task
         - Note
Learn more about "@auth" authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules


✅ GraphQL schema compiled successfully.

Edit your schema at /Users/lancecleveland/WebstormProjects/alphastocker/amplify-datastore/amplify/backend/api/amplifyDatasource/schema.graphql or place .graphql files in a directory at /Users/lancecleveland/WebstormProjects/alphastocker/amplify-datastore/amplify/backend/api/amplifyDatasource/schema

Successfully generated models. Generated models can be found in /Users/lancecleveland/WebstormProjects/alphastocker/amplify-datastore/src

This will create a models directory in the source directory at amplify-datastore/src/models.

We will also end up with a modified amplify-datastore/package.json with the new libs:

{
    "name": "amplify-datastore",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
        "@aws-amplify/core": "^5.0.11",
        "@aws-amplify/datastore": "^4.0.11",
        "@testing-library/jest-dom": "^5.16.5",
        "@testing-library/react": "^13.4.0",
        "@testing-library/user-event": "^13.5.0",
        "antd": "^5.1.6",
        "react": "^18.2.0",
        "react-color": "^2.19.3",
        "react-dom": "^18.2.0",
        "react-scripts": "5.0.1",
        "web-vitals": "^2.1.4"
    },
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject",
        "amplify-modelgen": "node amplify/scripts/amplify-modelgen.js",
        "amplify-push": "node amplify/scripts/amplify-push.js"
    },
    "eslintConfig": {
        "extends": [
            "react-app",
            "react-app/jest"
        ]
    },
    "browserslist": {
        "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
        ],
        "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
        ]
    },
    "devDependencies": {
        "ini": "^1.3.5",
        "inquirer": "^6.5.1"
    }
}

Push Amplify App To The Cloud

Run amplify init and amplify push.

amplify init
... answer prompts (see below)

amplify push
... answer prompts (see below)

Amplify Init Prompts

On the first go select…

  • Environment : dev (default)
  • Editor: IntelliJ IDEA
  • Auth mode: AWS Profile
  • Which Profile: aws-amplified (one I had from a prior project, can choose default)
The Amplify Init process

Some suggested commands:

Your project has been successfully initialized and connected to the cloud!

Some next steps:

"amplify status" 
will show you what you've added already and if it's locally configured or deployed

"amplify add <category>" 
will allow you to add features like user login or a backend API

"amplify push" 
will build all your local backend resources and provision it in the cloud

"amplify console" 
to open the Amplify Console and view your project status

"amplify publish" 
will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Amplify Push Prompts

  • Continue? Y
  • Generate GraphQL Code? N
    Per video: “Since we are using the DataStore API, choose no”
Pushing to the cloud

Updating The React App Template Code

Before starting this step, I initialized a local git repo and committed the current state of the app to a repo. This gives us a good baseline to rewind to without having to start over.

Now we need to connect the AWS Amplify Datastore to the skeleton React App. Time to edit some code.

The React App starts here: amplify-datastore/src/index.js

This is where the video is starting to show it’s age. Some of the distribution elements in Ant Design are missing. Here is what was added locally to the index.js just below the pre-existing imports.

import {Amplify} from "@aws-amplify/core";
import config from './aws-exports';
Amplify.configure(config);

The current index.js at this stage:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import {Amplify} from "@aws-amplify/core";
import config from './aws-exports';
Amplify.configure(config);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Following the video, we will overwrite the main App.js — however this app in the video is designed for a simple messaging data model that was neglected earlier in the setup process. We will have to go back and fix that.

Here is the new app code for amplify-datastore/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, updateMessage] = useState([])
    const [showPicker, updateShowPicker] = useState(false)

    useEffect(() => {
        fetchMessages()
        const subscription = DataStore.observe(Message).subscribe(()=>fetchMessages())
        return () => subscription.unsubscribe();
    });

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

    async function fetchMessages() {
        const messages = await DataStore.query(Message)
        updateMessage(messages)
    }

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

    return (
        <div style={container}>
            <h1 style={heading}>Messages</h1>
            <Input
                onChange={onChange}
                name='title'
                placeholder='Messsage title'
                value={formState.title}
                style={input}
                />
            <div>
                <Button
                    onClick={() => updateShowPicker(!showPicker)}
                    style={button}>Toggle 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

Slight Data Model Clean Up Here…

Above where I have “Revise the model here”, if you did that in order you’ll be good to start the app. I skipped that step and now have to go fix it. I revised the schema.graphql file to replace the ToDo list example with the message example used in the video.

I edited the file and hope to rebuild with a model gen command that fixes everything. Let’s see how good these Datastore and Amplify CLI tools are…

npm run amplify-modelgen
amplify push

Maybe we needed to run amplify init again? I’m hoping the first time that the models and services were invoked, so amplify push will send the new model and DataStore will know how to version it and replace the ToDo with the Message model.

Model Gen seems to have worked

The model gen worked and the push looked promising, but alas no… it broke. It did indicate it needed to remove the old ToDo model tables, but clearly it did not like deleting all the tables and putting in a new one:

Amplify push data model update broke… looks like we need a special “blast it all” flag…
 amplify push --allow-destructive-graphql-schema-updates 

Run The App

Execute npm start to run the app:

npm start

You should see a rudimentary web app come up:

The demo messages app.

No Data Listener

Now opening the browser in two separate windows, like shown in the demo video, should populate the message list in the new browser window. Alas this does not happen. Something is up in our initial app.

The demo video shows and import of ./serviceWorker in the main index.js. While this is a listener of some sort, it is NOT present in the current version of the Amplify tools.

Checking the AWS Amplify console it looks like no data is actually being synced/sent to the cloud.

Attempted Fix #1

Let’s see if we can get this talking to DynamoDB on the cloud. I can see the data object and structure on AWS but no content.

amplify env add 
amplify api update
npm start
Amplify add dev environ
Amplify API Update

No change.

Attempted Fix #2

Maybe the user credential I chose (not default) is breaking things. The next fix attempt from hints over at AppSync …

amplify add codegen --apiId 7ed66z6fkfhallyiwzwctbcryy  

This barfed out the following error:

User: arn:aws:iam::744590032041:user/amplify-react-amplified is not authorized to perform: appsync:GetGraphqlApi on resource: arn:aws:appsync:us-east-1:744590032041:apis/7ed66z6fkfhallyiwzwctbcryy

To fix this permission error – go to IAM on AWS and create a new security group “react-amplify-apps”. Assign the aws-amplify user to the new group.
In that group add the following policies:

AWSAppSyncAdministrator (this one finally allowed access)

These may have helped but need to be "dialed down" most likely:
AdministratorAccess-Amplify
AmazonDynamoDBFullAccess
AWSAppSyncInvokeFullAccess

Re-run the codegen above and we get a bunch of new code added to our app:

amplify add codegen worked after fixing my custom aws-amplify user to have more permissions.

And now we have the GraphQL source subdir shown in many Amplify examples at amplify-datastore/src/graphql/queries.js

Let’s check out our amplify environment

amplify status

Well, damn, this isn’t right…

App showing two data sources one with -dev suffix.

Patch Attempt 3

amplify delete
amplify init
npx amplify-app@latest  
npm run amplify-modelgen

This tossed an interesting warning…

No AppSync API configured. Please add an API

Go try to add that back in…

amplify add api

Answer the prompts and learn the data struct definition is missing.

Go back and edit amplify-datastore/amplify/backend/api/asdatastore/schema.graphql and add the model back in (copied from above) for the Messages model. It now looks a little different with the new default headers:

# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

type Message @model {
    id: ID!
    title: String!
    color: String
    createdAt: String
}
Answering the amplify add api prompts.
amplify status after rebuild

Answers to the amplify push command:

Answers to amplify push.

Woot Woot! Now for an article on doing this from scratch and getting it working on the first go! I’ll post that later.

Related Resources

This post is based heavily on the Amplify Getting Started documentation.

Amplify CLI Commands

  • amplify <category> <subcommand>
  • amplify push
  • amplify pull
  • amplify env <subcommand>
  • amplify configure
  • amplify console
  • amplify delete
  • amplify help
  • amplify init
  • amplify publish
  • amplify run
  • amplify status
  • amplify logout

Engage

Have tips on how to do this more effectively? Share in the comments below.

post image via pixabay

Leave a Reply

%d bloggers like this: