Skip to content

AWS Summit Dev Labs: Build a modern serverless web application in minutes using the AWS Amplify Framework

Notifications You must be signed in to change notification settings

awssgr/voterocket-lab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dev Labs: Build a modern serverless web application in minutes using the AWS Amplify Framework

Vote Rocket

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

Topics we'll be covering:

Prerequisites

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

If you get stuck or get an error

Set up

  • 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

Installing the CLI & initialising a new AWS Amplify project

Installing the CLI

Install the AWS Amplify CLI:

npm install -g @aws-amplify/cli

Getting started - create the application

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. Choose default - (The default profile was configured when you ran setup.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.

Adding a GraphQL API

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.

Using the generated AppSync API.

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.

Adding a mutation from within the AWS AppSync Console

Add some Candidates 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

Adding custom business logic to the GraphQL API

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 castVote to your GraphQL schema

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.

Add the castVote resolver templates:

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 the Candidate's @model directive earlier.
  • Amplify's generated queries, mutations and subscriptions Javascript source in ./src/graphql/ will be updated to reflect the new castVote functionality.

Test the resolver in the AWS AppSync console

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:

Creating the React front-end application

We'll now add the front-end HTML and Javascript files to our front-end.

Add dependencies

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 the front-end files

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

Try out the application

  • 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 menu Preview | 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 :-)

Recap

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.

Lab complete! Where to from here?

The lab itself is now complete.

Some optional topics to explore:

Examining the App.js file

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);

Fetching data from the GraphQL back-end

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 imported from ./graphql/* at the top of the file.

Real-time updates using GraphQL API subscriptions

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.

Host using the AWS Amplify Console

Use other Amplify Framework features and services

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.

Further reading


  1. 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 ↑]
  2. See Amplify Framework Custom Resolvers. You also get the benefit of using DynamoDB atomic counters. [return ↑]

About

AWS Summit Dev Labs: Build a modern serverless web application in minutes using the AWS Amplify Framework

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages