A Live Developer Journal

Deploy And Host A Full Stack React Web App

Following the Front-End Developer Learning Path on AWS. I'm documenting everything I did to build this mini project. There's nothing original here.

As a front-end developer, you build feature-rich web applications. You use popular front-end web frameworks (or vanilla JavaScript) to build the presentation layer of your app (the layout, positioning of text and images, colors, fonts, buttons etc). You also work with backend APIs and services to add interactivity to your web application.

Learning objectives:

Prerequisites

Host a React App

Create a React Application


npx create-react-app amplifyapp
cd amplifyapp
npm start

Initialize a Gitlab repository


git init
git remote add origin git@gitlab.com:username/reponame.git
git add .
git commit -m "start AWS practice React app."
git push origin master

Deploy our app with AWS Amplify

Automatically deploy code changes


import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1>Hello from V2</h1>
      </header>
    </div>
  );
}

export default App;


git add .
git commit -m “changes for v2”
git push origin master

We have just deployed a React application in the AWS Cloud by integrating with Github and using AWS Amplify. With AWS Amplify, we can continuously deploy our application in the cloud and host it on a globally-available CDN.

Initialize Local App

Now that we have initialized a new Amplify project in our account, we want to bring it down into our local environment so we can continue deployment and add new features.

Install the Amplify CLI

The Amplify Command Line Interface (CLI) is a unified toolchain to create AWS cloud services for your app, following a simple guided workflow (remove the `-g` flag if you don't want to install this globally on your system).
npm install -g @aws-amplify/cli

Configure the Amplify CLI

Amazon IAM (Identity and Access Management) enables you to manage users and user permissions in AWS. The CLI uses IAM to create and manage services programmatically on your behalf via the CLI.
amplify configure

Initialize the Amplify app

Since we have already created an Amplify project, we can use the app config that has already been created.


amplify init --appId your-app-id

? Enter a name for the project: amplifyapp
? Enter a name for the environment: dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that youre building: javascript
? What javascript framework are you using: react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command:  npm run-script build
? Start Command: npm run-script start
? Do you want to use an AWS profile? Y
? Please choose the profile you want to use: your-aws-profile

We have now initialized the Amplify project and are now ready to start adding features (like the user authentication flow next). To view your Amplify project in the dashboard at any time, you can run the amplify console command.

Add Authentication

Install the Amplify Libraries

We need two Amplify libraries for our project. The main aws-amplify library contains all of the client-side APIs for interacting with the various AWS services we'll be working with. The @aws-amplify/ui-react library contains framework-specific UI components. Install these libraries in the root of the project.

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

Create the authentication service

Using the Amplify CLI we just installed:


amplify add auth

? 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.

Deploy the authentication service

Deploy by running the Amplify push command amplify push --y

Configure the React project with Amplify resources

The CLI created and will continue to update a file called "aws-exports.js" located in the src directory of our project. We'll use this file to let the React project know about the different AWS resources that're available in our Amplify project.

Open src/index.js and add the following code below the last import:


import Amplify from 'aws-amplify';
import config from './aws-exports';
Amplify.configure(config);

Add the authentication flow in App.js

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


import React from 'react';
import logo from './logo.svg';
import './App.css';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react'

function App() {
  return (
    <div className="App"<
      <header<

        <img src={logo} className="App-logo" alt="logo" /<

        <h1>We now have Auth!<d/h1<

      </header<

      <AmplifySignOut /<

    </div*gt;
  );
}

export default withAuthenticator(App);

We've used the withAuthenticator component, which will scaffold out an entire user authentication flow allowing users to sign-up, sign-in, reset their password and comform sign in for multifactor authentication. We've also used the AmplifySignOut component which will render a Sign Out button.

Run the app locally

Run the app to see the new Authentication flow protecting the app: npm start

Deplay the changes to the live environment


git add .
git commit -m “Add auth.”
git push origin master

Add API and Database

The API we will be creating is a GraphQL API that will allow users to create, delete, and list notes.

API - Provides a programming interface that allows communication and interactions between multiple software intermediaries.

GraphQL - A query language and server-side API implementation based on a typed representation of our application. This API representation is declared using a schema based on the GraphQL type system.

Create a GraphQL API and database

Add a GraphQL API to your app by running the following command from the root of your project:


amplify add api

? Please select from one of the below mentioned services: GraphQL
? Provide API name: notesapp
? 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, I am done.
? Do you have an annotated GraphQL schema?  No
? Do you want a guided schema creation?  Yes
? What best describes your project: Single object with fields
? Do you want to edit the schema now? Yes

Open the GraphQL schema in your text editor: amplify/backend/api/myapi/schema.graphql and update the file with the following schema:


type Note @model {
  id: ID!
  name: String!
  description: String
}

Same the file, then go back to the terminal and hit enter to complete the API configuration step.

Deploy the API

Deploy the API: amplify push --y

The deploy command above will create the AppSync API, create a DynamoDB table, create the local GraphQL operations in a folder located at "src/graphql" that we can use to query the API.

To view the GraphQL API, run:


amplify console api

> Choose GraphQL

To view the Amplify app, run amplify console

Write front-end code to interact with the API

Now that the back-end has been deployed, we can write some code that will allow users to create, list and delete notes. Update src/App.js with the following code:


import React, { useState, useEffect } from 'react';
import './App.css';
import { API } from 'aws-amplify';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
import { listNotes } from './graphql/queries';
import { createNote as createNoteMutation, deleteNote as deleteNoteMutation } from './graphql/mutations';

const initialFormState = { name: '', description: '' }

function App() {
  const [notes, setNotes] = useState([]);
  const [formData, setFormData] = useState(initialFormState);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await API.graphql({ query: listNotes });
    setNotes(apiData.data.listNotes.items);
  }

  async function createNote() {
    if (!formData.name || !formData.description) return;
    await API.graphql({ query: createNoteMutation, variables: { input: formData } });
    setNotes([ ...notes, formData ]);
    setFormData(initialFormState);
  }

  async function deleteNote({ id }) {
    const newNotesArray = notes.filter(note => note.id !== id);
    setNotes(newNotesArray);
    await API.graphql({ query: deleteNoteMutation, variables: { input: { id } }});
  }

  return (
    <div className="App">
      <h1>My Notes App</h1>
      <input
        onChange={e => setFormData({ ...formData, 'name': e.target.value})}
        placeholder="Note name"
        value={formData.name}
      />
      <input
        onChange={e => setFormData({ ...formData, 'description': e.target.value})}
        placeholder="Note description"
        value={formData.description}
      />
      <button onClick={createNote}>Create Note</button>
      <div style="marginBottom: 30">
        {
          notes.map(note => (
            <div key={note.id || note.name}>
              <h2>{note.name}</h2>
              <p>{note.description}</p>
              <button onClick={() => deleteNote(note)}>Delete note</button>
            </div>
          ))
        }
      </div>
      <AmplifySignOut />
    </div>
  );
}

export default withAuthenticator(App);

There are three main functions in our app:

Run the app

npm start

Add Storage

Create the storage service

To add image storage functionality, we'll use the Amplify storage category:


amplify add storage

? Please select from one of the below mentioned services: Content
? Please provide a friendly name for your resource that will be used to label this category in the project: imagestorage
? Please provide bucket name: your-unique-bucket-name
? Who should have access: Auth users only
? What kind of access do you want for Authenticated users? create, read, update, delete
? Do you want to add a Lambda Trigger for your S3 Bucket? N

Update the GraphQL schema

Open amplify/backend/api/notesapp/schema.graphql and update it with (save when done):


type Note @model {
  id: ID!
  name: String!
  description: String
  image: String
}

Deploy storage service and API updates

amplify push --y

Update the React app

Open src/App.js and add the Storage class to our Amplify imports:

import { API, Storage } from 'aws-amplify';

In the main App function, create a new onChange function to handle the image upload:


async function onChange(e) {
  if (!e.target.files[0]) return
  const file = e.target.files[0];
  setFormData({ ...formData, image: file.name });
  await Storage.put(file.name, file);
  fetchNotes();
}

Update the fetchNotes funciton to fetch an image if there is an image associated with a note:


async function fetchNotes() {
  const apiData = await API.graphql({ query: listNotes });
  const notesFromAPI = apiData.data.listNotes.items;
  await Promise.all(notesFromAPI.map(async note => {
    if (note.image) {
      const image = await Storage.get(note.image);
      note.image = image;
    }
    return note;
  }))
  setNotes(apiData.data.listNotes.items);
}

Update the createNote funciton to add the image to the local image array if an image is associated with the note:


async function createNote() {
  if (!formData.name || !formData.description) return;
  await API.graphql({ query: createNoteMutation, variables: { input: formData } });
  if (formData.image) {
    const image = await Storage.get(formData.image);
    formData.image = image;
  }
  setNotes([ ...notes, formData ]);
  setFormData(initialFormState);
}

Add an additional input to the form in the return block:


<input
  type="file"
  onChange={onChange}
/>

When mapping over the notes array, render an image if it exists:


{
  notes.map(note => (
    <div key={note.id || note.name}>
      <h2>{note.name}</h2>
      <p>{note.description}</p>
      <button onClick={() => deleteNote(note)}>Delete note</button>
      {
        note.image && <img src={note.image} style={width: 400} />
      }
    </div>
  ))
}

Run the app

npm start

We have:

Deleting Resources

Remove individual services:


amplify remove auth
amplify push

To delete the entire project: amplify delete