Getting Started With AWS Amplify
Table of content
  1. What is AWS Amplify?
  2. Planning out the app
  3. Basic Setup
  4. Frontend Setup
  5. Adding a GraphQL API
  6. Adding UI to the App
  7. Run it locally
  8. Adding authentication
  9. Deploy the App!

At TeaCode.io we really like our tea though at times its stock may run out. This has given us ideas for several impressive web apps. Let me take you on a journey of building and deploying a super simple tea management app with AWS Amplify.

What is AWS Amplify?

AWS Amplify provides developers with a set of tools and services for the rapid development of scalable full-stack applications. Amplify allows one to quickly configure a backend and connect it to your app and then deploy it! Sounds too good to be true? Well, let me show you how quickly you can set up, build and deploy a simple React.js tea management app using AWS services.

Planning out the app

Let’s first plan out the app so that we know what we are going to need. Firstly, we need a front-end app that we will build using React.js. We also want to store the data – DynamoDB with a GraphQL API will manage this for us. Finally, we want to have an authorization system and we will let AWS Cognito handle it.

AWS Amplify app map

Basic Setup

In order to use AWS Amplify you will need to create a free AWS account. Next, install the AWS amplify cli package using npm. You might need to run this with sudo depending on your system policies.

npm install -g @aws-amplify/cli

Once it is installed, run the configuration script.

amplify configure

It will take you through all the necessary steps including setting up an IAM user, which for the tutorial you should create with the AdministratorAccess policy. Remember to store the accessKeyId and the secretAccessKey as they are going to be required in the later steps.

Specify the AWS Region
? region:  # Your preferred region
Specify the username of the new IAM user:
? user name:  # User name for Amplify IAM user
Complete the user creation using the AWS console

Enter the access key of the newly created user:
? accessKeyId:  # YOUR_ACCESS_KEY_ID
? secretAccessKey:  # YOUR_SECRET_ACCESS_KEY
This would update/create the AWS Profile in your local machine
? Profile Name:  # (default)

Successfully set up the new user.
Add User AWS
Add User AWS

Frontend Setup

With the basics of amplify all done let’s configure our simple React app. Run the following commands:

npx create-react-app tea-management-app
cd tea-management-app

This will create a new React app in the tea-management-app directory. Now, from the root directory, we can initialize the backend using amplify. Simply run:

amplify init

During the initialization, you will be prompted to answer a couple of questions.

Enter a name for the project (tea-mangement-app)

# All AWS services you provision for your app are grouped into an "environment"
# A common naming convention is dev, staging, and production
Enter a name for the environment (dev)

# Sometimes the CLI will prompt you to edit a file, it will use this editor to open those files.
Choose your default editor

# Amplify supports JavaScript (Web & React Native), iOS, and Android apps
Choose the type of app that you're building (javascript)

What JavaScript framework are you using (react)

Source directory path (src)

Distribution directory path (build)

Build command (npm run build)

Start command (npm start)

# This is the profile you created with the `amplify configure` command in the introduction step.
Do you want to use an AWS profile

With Amplify initialized, let’s install the necessary amplify packages by running

npm install aws-amplify @aws-amplify/ui-react

The aws-amplify package provides you with all the necessary methods to interface with amplify and @aws-amplify/ui-react provides you with some React-specific UI components.

Finally, let’s configure the AWS Amplify client by adding the following code to src/index.js.

import Amplify from "aws-amplify";
import awsExports from "./aws-exports";
Amplify.configure(awsExports);

Adding a GraphQL API

To add an API it is as simple as running the following command in the root directory of your project.

amplify add api

For this tutorial accept the default values. As a result, you should have the following output.

? Please select from one of the below mentioned services:
# GraphQL
? Provide API name:
# teaapi
? Choose the default authorization type for the API:
# API Key
? Enter a description for the API key:
# demo
? After how many days from now the API key should expire:
# 7 (or your preferred expiration)
? Do you want to configure advanced settings for the GraphQL API:
# No
? Do you have an annotated GraphQL schema?
# No
? Choose a schema template:
# Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now?
# Yes

The CLI should open the GraphQL schema, however, should it fail to do so you can find it under amplify/backend/api/teaapi/schema.graphql. Replace the contents of the file with:

type Tea @model {
  id: ID!
  name: String!
  bags: Int!
}

Deploying the GraphQL API

Now let’s deploy the backend by simply running the push command.

amplify push

When pushing a new API version the CLI will ask you a couple of questions. You should answer them in the following way:

? Are you sure you want to continue? Y

# You will be walked through the following questions for GraphQL code generation
? Do you want to generate code for your newly created GraphQL API? Y
? Choose the code generation language target: javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions: src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions? Y
? Enter maximum statement depth [increase from default if your schema is deeply nested]: 2

GraphQL operations are automatically generated

As a result, AWS Amplify will generate all the necessary CRUD operations for you to interface with the GraphQL API. You can find these under src/graphql/. For example, under src/graphql/mutations.js you will find the mutations for creating, updating and deleting tea objects from the database. Let’s look at the createTea mutation.

export const createTea = /* GraphQL */ `
  mutation CreateTea(
    $input: CreateTeaInput!
    $condition: ModelTeaConditionInput
  ) {
    createTea(input: $input, condition: $condition) {
      id
      name
      bags
      createdAt
      updatedAt
    }
  }
`;

It provides an interface for you to call from your frontend app to create a Tea object. It accepts an input of type CreateTeaInput which is automatically generated and defined in the schema.graphql which you can locate under  #current-cloud-backend/api/teaapi/build/schema.graphql. In this file, you should be able to find the CreateTeaInput definition. It should be:

input CreateTeaInput {
  id: ID
  name: String!
  bags: Int!
}

This means that the CreateTeaInput requires a name as a string and bags as an int. The id of type ID is optional. You will also find the definitions of the remaining objects in the schema.graphql. Note, however, that these are generated automatically, so any changes that you make to these definitions will be overwritten when you call the amplify push command. At the same time, this means that when you change something in the definition of the model you need to push the changes to get the updated version of your functions.

Adding UI to the App

Now that our backend is setup we can build the frontend of our tea management app. We will create a form for adding new types of tea with the specified number of bags and display a list of the available teas that you can either drink or delete.

Open src/App.js and replace the code with the following code:

/* src/App.js */
import React, { useEffect, useState } from 'react'
import Amplify, { API, graphqlOperation } from 'aws-amplify'
import { createTea, deleteTea, updateTea } from './graphql/mutations'
import { listTeas } from './graphql/queries'

import awsExports from "./aws-exports";
Amplify.configure(awsExports);

const initialState = { name: '', bags: '' }

const App = () => {
  const [formState, setFormState] = useState(initialState)
  const [teas, setTeas] = useState([])

  useEffect(() => {
    fetchTeas()
  }, [])

  function setInput(key, value) {
    setFormState({ ...formState, [key]: value })
  }

  async function fetchTeas() {
    try {
      const teaData = await API.graphql(graphqlOperation(listTeas))
      const teas = teaData.data.listTeas.items
      
      setTeas(teas)
    } catch (err) { console.log('error fetching teas') }
  }

  async function addTea() {
    try {
      if (!formState.name || !formState.bags) return
      
      const tea = { ...formState }
      
      setTeas([...teas, tea])
      setFormState(initialState)
      
      await API.graphql(graphqlOperation(createTea, {input: tea}))
      
      fetchTeas()
    } catch (err) {
      console.log('error creating tea:', err)
    }
  }

  async function removeTea(index) {
    try {
      if (teas.length > index) {
        const teaId = {id: teas[index].id}
        
        teas.splice(index, 1)
        setTeas([...teas])
        
        await API.graphql(graphqlOperation(deleteTea, {input: teaId}))
      }
    } catch (err) {
      console.log('error deleting tea:', err)
    }
  }

  async function drinkTea(index) {
    try {
      if (teas.length > index) {
        let tea = teas[index]
        const newCount = parseInt(tea.bags) - 1

        if (newCount <= 0) {
          teas.splice(index, 1)
          setTeas([...teas])

          await API.graphql(graphqlOperation(deleteTea, {input: {id: tea.id}}))
        } else {
          tea.bags = newCount
          teas.splice(index, 1, tea)

          tea = {
            id: tea.id,
            name: tea.name,
            bags: tea.bags
          }

          setTeas([...teas])
            
          await API.graphql(graphqlOperation(updateTea, {input: tea}))
        }
      }
    } catch (err) {
      console.log('error drinking tea:', err)
    }
  }

  return (
    <div style={styles.container}>
      <h2>Tea Management App 🌿</h2>
      <div style={styles.inputContainer}>
      <input
        onChange={event => setInput('name', event.target.value)}
        style={styles.input}
        value={formState.name}
        placeholder="Name"
      />
      <input
        type="number"
        onChange={event => setInput('bags', event.target.value)}
        style={styles.input}
        value={formState.bags}
        placeholder="# of bags"
      />
      </div>
      <button style={styles.button} onClick={addTea}>+ Add Tea</button>
      <div style={styles.teaContainer}>
        <h4>Currently Available Tea</h4>
        {
          teas.map((tea, index) => (
            <div key={tea.id ? tea.id : index} style={styles.tea}>
              <div>
                <p style={styles.teaName}>{tea.name}</p>
                <p style={styles.teaBags}>{tea.bags} Bags</p>
              </div>
              <div>
                <button style={styles.drinkButton} onClick={() => drinkTea(index)}>Drink</button>
                <button style={styles.deleteButton} onClick={() => removeTea(index)}>Delete</button>
              </div>
            </div>
          ))
        }
      </div>
    </div>
  )
}

const styles = {
  container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 },
  inputContainer: {display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexDirection: 'row', width: '100%'},
  input: { boxSizing: 'border-box', borderRadius: 5, maxWidth: 195, border: 'none', backgroundColor: '#efefef', marginBottom: 10, padding: 8, fontSize: 18 },
  teaContainer: {marginTop: 25},
  tea: {  marginBottom: 10, marginTop: 10, padding: 10, backgroundColor: '#eee', borderRadius: 5, display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'},
  teaName: { fontSize: 20, fontWeight: 'bold', marginTop: 0, marginBottom: 10 },
  teaBags: { marginBottom: 0, marginTop: 0, color: '#666', fontStyle: 'italic' },
  button: { backgroundColor: '#27ae60', border: 'none', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px', borderRadius: 5, cursor: 'pointer'},
  drinkButton: { backgroundColor: '#27ae60', color: 'white', border: 'none', borderRadius: 5, fontSize: 14, height: 30, cursor: 'pointer', marginRight: 5},
  deleteButton: { backgroundColor: '#e76558', color: 'white', border: 'none', borderRadius: 5, fontSize: 14, height: 30, cursor: 'pointer'},
}

export default App

Let’s break down some of the functions that we have here.

  • fetchTeas – it fetches a list of teas using the aws-amplify API module and the listTeas query function.
  • addTea – it calls the createTea mutation using the aws-amplify API module based on the data provided in the form.
  • removeTea – it calls the deleteTea mutation using the aws-amplify API module using the id of the tea object. It is called when the delete button is clicked.
  • drinkTea – it updates the tea object by removing one bag. If the number of bags reaches 0, the tea object is removed.

All of these functions integrate with the API and the database making it straightforward for frontend developers to build some functionalities without diving deep into the matters of the backend.

Run it locally

To check if everything works as intended run the app locally by calling:

npm start

You should see the form for adding tea. Once you add a couple of teas, your app should look something like this:

UI of the Tea Management AWS App

Adding authentication

Now let’s see how easy it is to add authentication to your app. AWS provides the Amazon Cognito service which Amplify uses as its main authentication provider. It allows you to manage all user-related business without the need of setting up the whole system by hand! Just like with the API it is a simple as running:

amplify add auth

and answering the questions

? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in? Username
? Do you want to configure advanced settings?  No, I am done.

and finally you need to push the new service

amplify push

Authentication UI works out of the box

The @aws-amplify/ui-react package provides you with a ready solution for the authentication UI all you need to do is import the higher-order component

import { withAuthenticator } from '@aws-amplify/ui-react'

and wrap the main App component

export default withAuthenticator(App)

Now start your app and you should now see a login and registration UI.

Authentication UI

Once you create an account you can log in and have access to the tea management app!

Deploy the App!

Now that the app is all setup let’s deploy it to be available remotely. For this, we need to add a hosting

amplify add hosting

and answer the questions

? Select the plugin module to execute: # Hosting with Amplify Console (Managed hosting with custom domains, Continuous deployment)
? Choose a type: # Manual Deployment

finally, we can publish the app

amplify publish

The app is now online!

AI/ML Developer at TeaCode