Announcing the AWS Amplify CLI toolchain. Click here to read more.

React & React Native

This tutorial walks you through how to use AWS Amplify to build a React application. You can use a similar process with a React Native application (omitting hosting).

Installation

$ npm install -g @aws-amplify/cli
$ amplify configure

If you’re using Windows, we recommend the Windows Subsystem for Linux.

  • Ensure you have Create React App installed.
  • Create a new project as follows:
    npx create-react-app myapp
    cd myapp

Getting Started with the CLI

To get started, initialize your project in the new directory:

amplify init

After you answer the provided questions, you can use amplify help at any time to see the overall command structure, and amplify help <category> to see actions for a specific category.

The Amplify CLI uses AWS CloudFormation, and you can add or modify configurations locally before you push them for execution in your account. To see the status of the deployment at any time, run amplify status.

Publishing Your Web App

Without making any changes to your React application, add web hosting as follows:

amplify add hosting

You would be prompted next to select the environment setup. Select DEV (S3 only with HTTP) for quick prototyping and testing, and once production ready you could run the amplify update hosting command to publish your app to Amazon CloudFront (a CDN service).

Note: when using the PROD option there could be a 15-20 minute delay for the CDN setup and content replication.

When you’re prompted for information, such as the bucket name or application files, you can use the default values by pressing Enter.

Note You can use an order alias to add or remove category features. You can also run amplify hosting add.

Run amplify status to see that status (not deployed). Next, build and deploy your site by running amplify publish or amplify publish --invalidate-cache - for cache invalidation in the distribution network (if CloudFront is added via the hosting category). After it’s complete, your application is available in an S3 hosting bucket for testing. It’s also fronted with an Amazon CloudFront distribution. (if it is added via the hosting category in the prior bucket)

Add Auth

Now that your app is in the cloud, you can add some features like enabling users to register for your site and log in. Run amplify add auth and select the Default configuration. This adds the auth resource configurations locally in your amplify/backend/auth directory.

Run amplify push to provision your auth resources in the cloud. The ./src/aws-exports.js file that’s created has all of the appropriate cloud resources defined for your application.

Next, add the Amplify library to your web application as follows:

yarn add aws-amplify aws-amplify-react

If integrating with a React Native app, use:

yarn add aws-amplify aws-amplify-react-native
react-native link amazon-cognito-identity-js # DO NOT run this when using Expo or ExpoKit

Edit ./src/App.js to include the Amplify library, configurations, and React HOC. Then, initialize the library as follows:

import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
import { withAuthenticator } from 'aws-amplify-react'; // or 'aws-amplify-react-native';

Amplify.configure(awsconfig);

As of aws-amplify-react@4.x.x, the Authenticator is not styled. To include default styling in JavaScript, add this line before Amplify.configure:

import '@aws-amplify/ui/dist/style.css';

Wrap the default App component using withAuthenticator at the bottom of the file as follows:

export default withAuthenticator(App, true);

You can now use amplify publish to build and publish your app again. This time you’ll be able to register a new user and sign in before opening the main application.

API & property details for the Authenticator and withAuthenticator HOC are available in the Authentication Guide.

SignUp Configuration

The SignUp component provides your users with the ability to sign up. It is included as part of the Authenticator component.

Usage: <Authenticator signUpConfig={ signUpConfig }/>

It can also be used as part of the authentication HOC: export default withAuthenticator(App, { signUpConfig });

The SignUp Component accepts a ‘signUpConfig’ object which allows you to customize it.

Attribute Type Description Default Required
header string the component header 'Create a new account' no
signUpFields array see below see below no
defaultCountryCode string the preselected value in the country code dropdown '1' no
hideAllDefaults boolean determines whether all default signup fields are to be hidden. This works in conjunction with the signUpFields attribute if there is no signUpFields attribute, defaults to false no
hiddenDefaults array determines whether particular default fields are hidden N/A (possible values include 'username', 'password', 'phone_number', and 'email') no

The signUpFields array in turn consist of an array of objects, each describing a field that will appear in sign up form that your users fill out:

Attribute Type Description Possible Values
label string label for the input field N/A
key string key name for the attribute as defined in the User Pool N/A
required boolean whether or not the field is required N/A
displayOrder number number indicating the order in which fields will be displayed N/A
type string the type attribute for the html input element ‘string’, ‘number’, ‘password’, etc
custom boolean flag which indicates whether or not the field is ‘custom’ in the User Pool N/A

A Sample signUpFields attribute would look like the following:

const signUpConfig = {
  header: 'My Customized Sign Up',
  hideAllDefaults: true,
  defaultCountryCode: '1',
  signUpFields: [
    {
      label: 'My custom email label',
      key: 'email',
      required: true,
      displayOrder: 1,
      type: 'string'
    },
    ... // and other custom attributes
  ]
};

export default withAuthenticator(App, { signUpConfig });

Sign up/in with email/phone number

If the user pool is set to allow email addresses/phone numbers as the username, you can then change the UI components accordingly by using usernameAttributes (learn more about the setup).

When you are using email as the username:

import { withAuthenticator, Authenticator } from 'aws-amplify-react';

// When using Authenticator
class App {
  // ...

  render() {
    return (
      <Authenticator usernameAttributes='email'/>
    );
  }
}

export default App;

// When using withAuthenticator
class App2 {
  // ...
}

export default withAuthenticator(App2, { usernameAttributes: 'email' });

When you are using phone number as the username:

import { Authenticator, withAuthenticator } from 'aws-amplify-react';

class App {
  // ...

  render() {
    return (
      <Authenticator usernameAttributes='phone_number'/>
    );
  }
}

export default App;

// When using withAuthenticator
class App2 {
  // ...
}

export default withAuthenticator(App2, { usernameAttributes: 'phone_number' });

Note: If you are using custom signUpFields to customize the username field, then you need to make sure either the label of that field is the same value you set in usernameAttributes or the key of the field is username.

For example:

import React, { Component } from 'react';
import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
import { withAuthenticator } from 'aws-amplify-react';

Amplify.configure(awsconfig);

class App extends Component {}

const signUpConfig = {
  header: 'My Customized Sign Up',
  hideAllDefaults: true,
  defaultCountryCode: '1',
  signUpFields: [
    {
      label: 'My user name',
      key: 'username',
      required: true,
      displayOrder: 1,
      type: 'string'
    },
    {
      label: 'Password',
      key: 'password',
      required: true,
      displayOrder: 2,
      type: 'password'
    },
    {
      label: 'PhoneNumber',
      key: 'phone_number',
      required: true,
      displayOrder: 3,
      type: 'string'
    },
    {
      label: 'Email',
      key: 'email',
      required: true,
      displayOrder: 4,
      type: 'string'
    }
  ]
};
const usernameAttributes = 'My user name';

export default withAuthenticator(App, {
  signUpConfig,
  usernameAttributes
});

Add Analytics and Storage

Next, we’ll add some features, like tracking user behavior analytics and uploading/downloading images in the cloud.

Before starting, to enable analytics in amplify, we need to associate user to an identity pool. Run amplify add auth and configure authentication with using manual configuration.

Start by running amplify add analytics in your project. You can enable analytics for authenticated users only, or for users that aren’t authenticated. You would be prompted to ask whether you want to allow guests and unauthenticated users to send analytics events, so you can choose Yes. You can also try a new project without authentication configured to test this feature.

Run amplify add storage and then select Content (Images, audio, video, etc.). You’ll then be prompted for authorization related questions. Choose Auth and guest users to give both authorized and guest users access. In the next prompts, based on your previous selection you would be asked to configure read/write permissions for the authorized and guest users. When complete, run amplify push to create the cloud resources.

Edit your App.js file in the React project again and modify your imports so that the Analytics and Storage categories are included in addition to the S3Album component, which we’ll use to upload and download photos.

import Amplify, { Analytics, Storage } from 'aws-amplify';
import { withAuthenticator, S3Album } from 'aws-amplify-react';

The Analytics category automatically tracks user session data such as sign-in events. However, you can record custom events or metrics at any time. You can also use the Storage category to upload files to a private user location after someone has logged in. First, add the following line after Amplify.configure() has been called:

Storage.configure({ level: 'private' });

Next, add the following methods before the component’s render method as follows:

  uploadFile = (evt) => {
    const file = evt.target.files[0];
    const name = file.name;

    Storage.put(name, file).then(() => {
      this.setState({ file: name });
    })
  }

  componentDidMount() {
    Analytics.record('Amplify_CLI');
  }

Finally, modify the render method so that you can upload files and also view any of the private photos that have been added for the logged in user as follows:

  render() {
    return (
      <div className="App">
        <p> Pick a file</p>
        <input type="file" onChange={this.uploadFile} />
        <S3Album level="private" path='' />
      </div>
    );
  }

Save your changes and run amplify publish. You’ve already pushed the changes earlier so just the local build is created and uploaded to the hosting bucket. Log in (if necessary) and upload photos, which are protected by the user. You can refresh the page to view the photos after you upload them.

Add GraphQL Backend

Now that your application is set up, it’s time to add a backend API with data that can be persisted in a database. The Amplify CLI comes with a GraphQL Transformer that converts annotated GraphQL schema files into the appropriate AWS CloudFormation template based on your data requirements. This includes options such as the following:

  • @model for storing types in Amazon DynamoDB.
  • @auth to define different authorization strategies.
  • @connection for specifying relationships between @model object types.
  • @searchable for streaming the data of an @model object type to Amazon Elasticsearch Service.

To get started run amplify add api and select GraphQL. When prompted, choose Amazon Cognito User Pool and the project will leverage your existing authentication setup. For annotated schema, choose No. For guided schema creation, choose Yes.

The guided steps provide some default schemas that are pre-annotated to help you learn. The following steps take you through the Single object with fields option, but feel free to revisit these steps later in another project. If you choose this option you’ll see the following annotated schema in your text editor:

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

This is the GraphQL schema that you’ll deploy to AWS AppSync. If you’re familiar with GraphQL you can rename or add fields and types, but you’ll need to change the client code too. When you’re ready press Enter in the CLI and then run amplify push.

After the deployment is complete, open your App.js again and update the import to include both the API category and graphqlOperation method as follows:

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

Add the following query and mutations in your code, before the class App extends Component {...} definition as follows:

const listTodos = `query listTodos {
  listTodos{
    items{
      id
      name
      description
    }
  }
}`;

const addTodo = `mutation createTodo($name:String! $description: String!) {
  createTodo(input:{
    name:$name
    description:$description
  }){
    id
    name
    description
  }
}`;

Now, inside the App component add the following two methods before the render() method:

todoMutation = async () => {
  const todoDetails = {
    name: 'Party tonight!',
    description: 'Amplify CLI rocks!'
  };

  const newTodo = await API.graphql(graphqlOperation(addTodo, todoDetails));
  alert(JSON.stringify(newTodo));
};

listQuery = async () => {
  console.log('listing todos');
  const allTodos = await API.graphql(graphqlOperation(listTodos));
  alert(JSON.stringify(allTodos));
};

You can now make GraphQL calls from your application. Update the render() method so that it has the following buttons to invoke the mutation and query:

  render() {
    return (
      <div className="App">
        <p> Pick a file</p>
        <input type="file" onChange={this.uploadFile} />
        <button onClick={this.listQuery}>GraphQL Query</button>
        <button onClick={this.todoMutation}>GraphQL Mutation</button>
        <S3Album level="private" path='' />
      </div>
    );
  }

Save the file and run amplify publish. After the backend is deployed, you can choose GraphQL Mutation to enter data into the database and GraphQL Query to retrieve a list of all entries. You can validate this in the AWS AppSync console too.

Add REST API Calls to a Database

For this example, we use a REST backend with a NoSQL database. Run amplify add api and follow the prompts. Select the REST option and provide a friendly name for your API, such as myapi or something else that you can remember. Use the default /items path and choose Create a new lambda function. Choose the option titled CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB) when prompted. This creates an architecture using Amazon API Gateway with Express running in an AWS Lambda function that reads and writes to Amazon DynamoDB. You can modify the routes in the Lambda function later to meet your needs and update it in the cloud.

Since you do not have a database provisioned yet, the CLI workflow prompts you for this information. Alternatively, you can run amplify add storage beforehand to create a DynamoDB table and use it in this setup. When the CLI prompts you for the primary key structure, use an attribute named id of type String. Don’t select any other options like sort keys or global secondary indexes (GSIs).

Next, for the API security type questions, choose Yes when prompted for Restriction of API access. Similar to the storage category, when prompted for Who should have access?, choose Auth and guest users to give both authorized and guest users access. In the next prompts, based on your previous selection you would be asked to configure read/write permissions for authorized and guest user.

In the React project, edit your App.js file again and modify your imports so that the API category is included so that you can make API calls from the app.

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

In App.js, add the following code before the render() method and update myapi if you used an alternative name during the setup:

post = async () => {
  console.log('calling api');
  const response = await API.post('myapi', '/items', {
    body: {
      id: '1',
      name: 'hello amplify!'
    }
  });
  alert(JSON.stringify(response, null, 2));
};
get = async () => {
  console.log('calling api');
  const response = await API.get('myapi', '/items/object/1');
  alert(JSON.stringify(response, null, 2));
};
list = async () => {
  console.log('calling api');
  const response = await API.get('myapi', '/items/1');
  alert(JSON.stringify(response, null, 2));
};

Update the render() method to include calls to the following methods:

  render() {
    return (
      <div className="App">
        <p> Pick a file</p>
        <input type="file" onChange={this.uploadFile} />
        <button onClick={this.post}>POST</button>
        <button onClick={this.get}>GET</button>
        <button onClick={this.list}>LIST</button>

        <S3Album level="private" path='' />
      </div>
    );
  }

Save the file and run amplify publish. After the API is deployed along with the Lambda function and database table, your app is built and updated in the cloud. You can then add a record to the database by choosing POST, and then using GET or LIST to retrieve the record, which has been hard coded in this simple example.

In your project directory, open ./amplify/backend/function and you’ll see the Lambda function that you created. The app.js file runs the Express function and all of the HTTP method routes are available for you to manipulate. For example, the API.post() in your React app corresponded to the app.post(path, function(req, res){...}) code in this Lambda function. If you choose to customize the Lambda function, you can update it in the cloud using amplify push.

Adding XR Sumerian Scene

To load Amazon Sumerian scenes you will need to activate the Amplify Auth category.

$ amplify add auth

The ./src/aws-exports.js file that’s created has all of the appropriate cloud resources defined for your application. Edit ./src/App.js to include the Amplify library and configurations. Then, initialize the library as follows:

import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
import { SumerianScene } from 'aws-amplify-react';
import scene1Config from './sumerian_exports_<sceneId>'; // This file will be generated by the Sumerian AWS Console

Amplify.configure({
  ...awsconfig,
  XR: {
    region: 'us-east-1', // Sumerian region
    scenes: {
      scene1: {
        // Friendly scene name
        sceneConfig: scene1Config // Scene configuration from Sumerian publish
      }
    }
  }
});

Update the render() method to include the Sumerian Scene component:

Note: The UI component will inherit the height and width of the direct parent DOM element. Make sure to set the width and height styling on the parent DOM element to your desired size.

  render() {
    return (
      <div className="App">
        // sceneName: the configured friendly scene you would like to load
        <SumerianScene sceneName="scene1" />
      </div>
    );
  }

See the XR documentation for information on creating and publishing a Sumerian scene.

Testing Serverless Functions

Amplify CLI supports local testing of Lambda functions. Run amplify status to get the resource name of the Lambda function created earlier, and then run the following:

amplify function invoke <resourcename>

In this case, the function runs, but it doesn’t exit because this Lambda example starts an Express server which you need to manually close when testing it from the CLI. Use ctrl-c to close and open the ./amplify/backend/function/resourcename directory to see the local structure that is packaged for Lambda invocation from API Gateway. The Lambda function is inside the src directory, along with event.json, which is used for the amplify function invoke command you just ran. index.js is also in this directory, which is the main entry point for the Serverless Express library that echoed out the test event and instantiated the server inside app.js. Since the Express routes defined in app.js don’t have a path that’s called via the test event, it responded with a 404 message. For more information, see https://github.com/awslabs/aws-serverless-express.

Note on JWT Storage

Data is stored unencrypted when using standard storage adapters (localStorage in the browser and AsyncStorage on React Native). Amplify gives you the option to use your own storage object to persist data. With this, you could write a thin wrapper around libraries like:

  • react-native-keychain
  • react-native-secure-storage
  • Expo’s secure store