AWS Amplify makes it easy for you to create, configure, and implement scalable mobile and web apps powered by AWS. In this workshop we'll learn how to build the Vote Rocket voting web application with React and the Amplify Framework
- Getting started - create the application
- Adding a GraphQL API
- Adding custom business logic to the GraphQL API
- Creating the React front-end application
- Recap
- Lab complete! Where to from here?
This lab assumes the following:
- It's being executed in an AWS Cloud9 instance provided by the lab co-ordinator.
- This repository has been cloned into the default
~/environment
directory at~/environment/voterocket-lab
- In case you get stuck or need a reference to compare your implementation with, the full source is available at https://github.com/awssgr/voterocket.
- Ask your lab co-ordinator for help.
- Raise an issue explaining the problem.
- Go to to AWS Console
- Check to see that you are in US East (N. Virginia) in the top right-hand corner.
- Go to Cloud9
- Go to Account Environments.
- Select Open IDE for AWS Ampligy Framework Lab:
After you have opened the IDE, open a new terminal.
Run the following to get started. This will configure the environment and clean any artifacts from this lab being run previously.
cd ~/environment/voterocket-lab
./setup.sh
Install the AWS Amplify CLI:
npm install -g @aws-amplify/cli
Note: Be sure to start the lab in the default Cloud 9 directory:
cd ~/environment
Next, create a new React app using the Create React App CLI. The create-react-app
library was installed when you ran setup.sh
earlier.
create-react-app voterocket
Change into the voterocket
directory & configure your project to work with the Amplify Framework:
cd voterocket
amplify init
You'll be prompted to answer some questions:
- Enter a name for the project
voterocket
- Enter a name for the environment
dev
- Choose your default editor:
None
- Choose the type of app that you're building
javascript
Please tell us about your project
- 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?
Yes
- Please choose the profile you want to use:
> default
. Choosedefault
- (Thedefault
profile was configured when you ransetup.sh
above)
The AWS Amplify CLI will initialise a new project inside your React project & you will see a new folder: amplify
. The files in this folder hold your project configuration.
To add a GraphQL API (AWS AppSync), we can use the following command:
amplify add api
You'll be prompted to answer some questions:
- Please select from one of the above mentioned services
GraphQL
- Provide API name:
voterocket
- Choose an authorization type for the API
API key
- 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 (e.g. “Todo” with ID, name, description)
- Do you want to edit the schema now? (Y/n)
Y
Voterocket's schema requires we have a Candidate
with an id
, name
, description
and count of votes
received.
When prompted (the file is at amplify/backend/api/voterocket/schema.graphql
), replace the schema with the following:
type Candidate @model {
id: ID!
name: String!
description: String
votes: Int!
}
Amplify CLI uses GraphQL Transform, which simplify the process of developing, deploying, and maintaining GraphQL APIs on AWS. Transforms are implemented using directives
This example uses the @model
directive, which by default will automatically configure these AWS resources.
Next, let's push the configuration to our account:
amplify push
- 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
The generated GraphQL (via the @model
directive can be found in amplify/backend/api/voterocket/build/schema.graphql
The generated Javascript source can be found in ./src/graphql/
and provides pre-built queries, mutations and subscriptions that can be imported directly into your React application.
The generated API can be found at https://console.aws.amazon.com/appsync. Ensure the region is set correctly :-)
In the AWS AppSync console, open your API & then click on Queries.
Paste the following into the console and run it.
query AllCandidates {
listCandidates {
items {
id
name
description
votes
}
}
}
It should return an empty list. This is because there is no data in the underlying DynamoDB database.
Add some Candidate
s using a mutation as below. This will add four entries and return a result:
mutation createCandidates {
candidate1: createCandidate(input: {name: "Lambda", description: "Run code without thinking about servers", votes: 0 }) {id, name, description, votes }
candidate2: createCandidate(input: {name: "DynamoDB", description: "Fast and flexible NoSQL database service", votes: 0 }) {id, name, description, votes }
candidate3: createCandidate(input: {name: "API Gateway", description: "Managed RESTful APIs", votes: 0 }) {id, name, description, votes }
candidate4: createCandidate(input: {name: "AppSync", description: "Managed GraphQL APIs", votes: 0 }) {id, name, description, votes }
}
Executing the AllCandidates
query above will now return the candidates created in the mutation.
We can also filter when querying. This is available because the Amplify CLI generated the transforms as described earlier.
query NameContainsLambda {
listCandidates(filter: {name: {contains: "Lambda"} }) {
items {
id
name
description
votes
}
}
}
Will return only the Candidate
whose name contains Lambda
Because this is a voting application we need to find a way to record a vote for a candidate. While we could use the updateCandidate
mutation and resolver that was generated for us, this relies on having to increment the value on the client. It can't be guaranteed that all clients will have the same value for the vote count—it's much more robust to do this server-side1..
This is how we would it if this were the AWS CLI:
aws dynamodb update-item \
--table-name Candidate \
--key '{ "id": {"S": "552e120b-3192-4cac-bb13-c8821472e6d6"} }' \
--update-expression 'set #votesField = #votesField + :i' \
--expression-attribute-values '{ ":i": { "N": "10"} }' \
--expression-attribute-names '{ "#votesField": "votes" }'
For our GraphQL API to execute a similar update-item
we need to create a custom resolver2..
Append the following to your schema.graphql
:
input CastVoteInput {
id: ID!
}
type Mutation {
castVote(input: CastVoteInput!): Candidate
}
type Subscription {
onCastVote: Candidate
@aws_subscribe(mutations: ["castVote"])
}
This will allow us to call a castVote
mutation that will increment the vote counter by 1, and enforce an input that has only the ID of the candidate. There is also a subscription type that will "push" real-time updates to a connected client when the mutation is called.
Copy the two .vtl
files in this lab's samples
folder into your project's ./amplify/backend/api/voterocket/resolvers
directory:
cp ~/environment/voterocket-lab/samples/Mutation.castVote.*.vtl ./amplify/backend/api/voterocket/resolvers/
If you open the Mutation.castVote.req.vtl
resolver in the editor you will see it looks a lot like aws dynamodb update-item
CLI command above.
We also need to tell Amplify to add these resolvers to your API by adding a new resolver resource to Amplify's CloudFormation templates.
Copy the CustomResources.json
file in this lab's samples
folder and overwrite the file at ./amplify/backend/api/voterocket/stacks/CustomResources.json
:
cp ~/environment/voterocket-lab/samples/CustomResources.json ./amplify/backend/api/voterocket/stacks/CustomResources.json
Run amplify push
to provision the custom resolvers.
You will be asked:
- Do you want to update code for your updated GraphQL API
Yes
- Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types. This will overwrite your current graphql queries, mutations and subscriptions
Yes
Two things will then happen behind the scenes:
- The custom resolvers will be added to the
amplify/backend/api/voterocket/build/resolvers/
along with the other auto-generated resolvers implied from theCandidate
's@model
directive earlier. - Amplify's generated queries, mutations and subscriptions Javascript source in
./src/graphql/
will be updated to reflect the newcastVote
functionality.
If you'd like to test the new resolver, navigate to the AWS AppSync console, open your API & then click on Queries. If you were already on this page in your browser, hit refresh to update the AWS console's state based on the new resolvers.
Paste the following into the console and run it (note to use an id
of a valid candidate in your database). You can find a valid id
(UUID) by running the listCandidates
GraphQL query as above.
mutation CastVote {
castVote(
input: {
id: "7f63b9cd-bd25-4c47-95a9-b530b2215c46"
}
)
{
id
votes
}
}
Each time you execute the mutation it will increment the votes
value by 1:
We'll now add the front-end HTML and Javascript files to our front-end.
We need to add the Amplify Framework dependencies to the app. We will also add Chart.js and the React wrapper for Chart.js to display our vote counts as a chart:
Run yarn add aws-amplify aws-amplify-react chart.js react-chartjs-2
Copy App.js
and index.html
files in this lab's samples
folder to your project: Note these will overwrite the existing files generated by create-react-app
earlier.
cp ~/environment/voterocket-lab/samples/App.js ./src
cp ~/environment/voterocket-lab/samples/index.html ./public
- Run
yarn start
will start the app.
You should see something like this:
You can now view voterocket in the browser.
Local: http://localhost:8080/
On Your Network: http://172.31.40.28:8080/
Note that the development build is not optimized.
To create a production build, use yarn build.
- Because you are using Cloud9,
localhost
is not available. Click the menuPreview | Preview Running Application
in the Cloud9 IDE to see the application running. You can also click the popout button to open it in a new browser window.
Pressing a button should cast a vote and the chart should update in real-time.
If you open another browser, or a new window, you should see the same chart update in that window, also in real-time :-)
In a short space of time you were able to create a working application from scratch with:
A scalable serverless back-end:
- Real-time GraphQL API, with a comprehensive set of queries, mutations and subscriptions for common CRUDL and custom operations
- Database to hold our state with a lock-free atomic counter to hold vote counts
- Custom business logic to connect the API to the database
A browser-based React front-end:
- Application-specific API client libraries that require minimal code to use for invoking the APIs.
- Real-time, event-driven updates to the page with React and subscriptions
Importantly this let's us focus on building what matters. Our business logic.
The lab itself is now complete.
Some optional topics to explore:
If you'd like some more insights into how the front-end works, open the App.js
file you copied in your editor. Note how the file is set up to use and configure the Amplify Framework:
import React, { Component } from 'react';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import * as subscriptions from './graphql/subscriptions';
import aws_exports from './aws-exports';
Amplify.configure(aws_exports);
Note also how the code in the componentDidMount
method of the App
class will query list of Candidates from the API and load it into the component's state when the page is first loaded.
const candidates = await API.graphql(graphqlOperation(queries.listCandidates))
this.setState({
candidates: candidates.data.listCandidates.items
})
The arguments to the graphqlOperation
method above (in this case queries.listCandidates
) are managed and generated automatically by the Amplify Framework and were import
ed from ./graphql/*
at the top of the file.
Also note how the subscribe
method below automatically binds the subscription to our user interface, and will upgrade the counters and chart in real-time.
API.graphql(graphqlOperation(subscriptions.onCastVote)).subscribe({
next: (voteCasted) => {
const id = voteCasted.value.data.onCastVote.id
const votes = voteCasted.value.data.onCastVote.votes
const candidates = this.state.candidates
const row = candidates.find( candidate => candidate.id === id );
row.votes = votes;
this.setState({ votes: candidates });
console.log("state:", this.state.candidates)
}
})
This will update the React state using the GraphQL subscription we added to the schema.graphql
file above, and again is updated and managed automatically by the Framework.
- You can also very easily deploy the application using AWS Amplify Console. Here are some examples that demonstrate how to do this.
The Amplify Framework is comprehensive and can let you integrate other services such as RESTful APIs, authentication, relational databases, analytics, storage, messaging, chat and others. For example, its really easy to add authentication using Amplify and Amazon Cognito. In a few lines of code you will be able to add an authentication step so that only logged-in users can access your application.
You can also use Amplify to develop applications for mobile apps using iOS and Android native APIs that could for example share the same back-end as this lab's.
- AWS Amplify framework: https://aws-amplify.github.io/
- AWS Amplify product page: https://aws.amazon.com/amplify/
- Awesome AWS Amplify: https://github.com/dabit3/awesome-aws-amplify
- The AWS AppSync community: https://github.com/aws/aws-appsync-community
- Full code for this lab at https://github.com/awssgr/voterocket
- A resolver is a function that converts the GraphQL payload to the underlying storage system protocol and executes if the caller is authorised to invoke it. Resolvers are comprised of request and response mapping templates, which contain transformation and execution logic. AWS AppSync uses mapping templates, which are written with the Apache Velocity Template Language (VTL) and interpreted by AWS AppSync. There is a resolver mapping template programming guide in the AWS AppSync Developer Guide that covers how to write resolvers in detail. [return ↑]
- See Amplify Framework Custom Resolvers. You also get the benefit of using DynamoDB atomic counters. [return ↑]