Build a voting website that doesn’t crash — part two. Now with Amplify.

In the first article, we built a serverless voting website in under an hour. Now let’s do it again, this time using AWS Amplify.

I set myself the challenge of building a highly scalable, serverless voting website in under an hour, and published the results in this article. While it worked as a proof of concept, the deployment process wasn’t ideal— I had to manually create a DynamoDB table, set up API Gateway and configure my IAM permissions, separately from bundling and hosting the front-end.

In this blog post, I’m approaching the problem again, still using Vue for the front-end, but this time introducing AWS Amplify as a way to co-ordinate the back-end pieces. The goal is to produce the same application but with fewer moving parts and a more maintainable architecture for modifications in the future.

Why do we need Amplify?

One of the issues with developing JavaScript front-ends with serverless back-ends is that you end up with two distinct code repositories, effectively creating two different applications. This makes it awkward to synchronize changes between the two, especially when handling multiple versions or different environments (for example, dev and prod).

Often there’s also a fair amount of boilerplate code involved — just setting up a basic CRUD interface for a DynamoDB table, it requires the same 100 lines of code to implement a REST API over and over again. It would be great to focus the effort on writing code for features that your customers care about, and not have to worry about the plumbing.

Amplify is the biggest news for web and mobile application developers in quite some time. It’s an opinionated framework from AWS designed for both web and mobile development, but for JavaScript developers includes all the most popular single-page application libraries and frameworks like React and Vue. It treats back-end services as components you can interact with, and manages all the configuration and interaction on your behalf.

Put simply, if you have a web front-end that needs a database, authorization and an S3 bucket, look no further — we can do all of this from Amplify in a couple of minutes and never need to venture into the AWS console. The original voting application used API Gateway to interact with a DynamoDB for managing voting tallies, and in this exercise we’re going to use the same components managed by the Amplify framework.

Learn more about the Amplify Framework at https://aws-amplify.github.io/docs/.

Getting started

First, make sure you have installed Node and the AWS CLI before we start. I’m using VSCode but feel free to use your favorite IDE.

Set up the Vue application:

  1. Create an empty directory for the application, and open a console here.
  2. Install the Vue CLI: npm install -g @vue/cli.
  3. Create a new Vue project: vue create pollcounter. Accept the project defaults.
  4. Change into the new directory: cd pollcounter.
Initialized Vue project in VSCode.

Set up Amplify:

  1. Now install Amplify and its Vue-specific components: npm install --save aws-amplify aws-amplify-vue.
  2. Initialize Amplify: amplify init. Accept the defaults and answer the questions depending upon your environment, for example:

This will create a number of resources in your AWS account, after which you are ready to use Amplify in the Vue project.

Configuring the back-end services

In this step, we will define the AWS services for the application. Last time, we did this manually in the AWS console, but it’s considerably easier here. The architecture of this application looks like this:

To build this in Amplify, we issue the command amplify add api and then answer the prompts in the configuration process:

An important part here is to provide meaningful names for the resources — it makes life much easier later in the process. Once you reach Create a new DynamoDB table, continue answering as shown below:

Finally, select “no” for adding another path and complete the setup. The file app.js should now be open in the editor — here you can see it has wired up basic methods for our API resource. The voting app only uses two methods:

  • GET: for retrieving vote totals.
  • POST: for casting a vote.

You can delete the DELETE and PUT methods since these will not be used.

Let’s replace the app.post method — in our case we want to update a vote count instead of inserting an item:

/************************************
* HTTP post method for insert object *
*************************************/
app.post(path, function(req, res) {
  if (userIdPresent) {
req.body['userId'] =
req.apiGateway.event.requestContext.identity.cognitoIdentityId || UNAUTH;
}
  const UpdateAttribute = req.query['vote'] === 'no' ? 'votesNo' : 'votesYes'
  let updateItemParams = {
TableName: tableName,
Key: {
partitionKey: 'poll-001',
sortKey: 'total'
},
UpdateExpression: `set ${UpdateAttribute} = ${UpdateAttribute} + :val`,
ExpressionAttributeValues:{
":val": 1
},
ReturnValues:"UPDATED_NEW"
}
  dynamodb.update(updateItemParams, (err, data) => {
if(err) {
res.statusCode = 500;
res.json({error: err, url: req.url, body: req.body});
} else {
res.json({success: 'post call succeed!', url: req.url, data: data})
}
)
})

This is now ready for deployment — enter amplify push into the console and it will deploy to your AWS account.

While the DynamoDB table has been created, it is empty — we need to add a single item to initialize the values for the poll. Open the table in the AWS console, click Create item and then add the following item:

Setting initial value for the poll counter.

Finally, adding the front-end

The original application is one VueJS component — it will be the same here with some minor changes. First, let’s wire up main.js.

Modifying main.js

The main layout uses Vue Bootstrap so run this command from a command-line (and your main project directory) to install the necessary components:

npm i bootstrap bootstrap-vue --save

Next, let’s add the necessary Amplify and Bootstrap packages into the main.js file to ensure these are loaded when the application starts up. Replace your entire main.js with the following:

import Vue from 'vue'
import App from './App.vue'
import Amplify from 'aws-amplify'
import awsmobile from './aws-exports'
Amplify.configure(awsmobile)
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')

App.vue

As with any Vue file, this is split into the template, script and style sections. Replace the existing template section with the following:

<template>
<div id="app" class="hello">
<h1>Welcome to the Serverless Voting App. Now with Amplify!</h1>
<h4>Click to vote on this very important issue.</h4>
<h4>You can vote as many times as you like. Click away!</h4>
<b-row align-h="center" class="mt-5">
<b-card-group deck>
<b-card bg-variant="success" text-variant="white" header="Vote Yes" class="text-center" footer-tag="footer">
<b-card-text>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</b-card-text>
<b-button size="lg" variant="primary" @click="vote('yes')">Button</b-button>
<em slot="footer">{{ votesYes }} voted</em>
</b-card>
<b-card bg-variant="danger" text-variant="white" header="Vote No" class="text-center" footer-tag="footer">
<b-card-text>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</b-card-text>
<b-button size="lg" variant="primary" @click="vote('no')">Button</b-button>
<em slot="footer">{{ votesNo }} voted</em>
</b-card>
</b-card-group>
</b-row>
<b-row align-h="center" class="mt-5">
<p>Questions? Ask James <a href="https://twitter.com/jbesw">@jbesw</a>.</p>
</b-row>
</div>
</template>

At the bottom of the file, replace the style section with this code:

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Now the interesting part is the script section. Amplify allows us to invoke an API without needing an http library (Axios or request, for example), and we can call the API by name without knowing the endpoint URL. We simply add an import at the top of the section and then using Amplify’s API interface, like this:

<script>
import { API } from 'aws-amplify'
export default {
name: 'app',
data() {
return {
apiName: 'pollCounterAPI',
votesYes: 0,
votesNo: 0
}
},
methods: {
vote: async function (vote) {
const init = {
queryStringParameters: {
vote
}
}
const response = await API.post(this.apiName, '/votes', init)
if (vote === 'yes') this.votesYes = response.data.Attributes.votesYes
if (vote === 'no') this.votesNo = response.data.Attributes.votesNo
},
updateVotes: async function () {
const response = await API.get(this.apiName, '/votes/poll-001')
this.votesNo = response[0].votesNo
this.votesYes = response[0].votesYes
}
},
created () {
this.updateVotes()
setInterval(this.updateVotes, 3000)
}
}
</script>

Deploying to the AWS cloud

Previously, you would have to run npm run build and then copy the resulting dist folder files to an S3 bucket you had prepared, and optionally set up CloudFront distribution. This process is error-prone since you must set various permissions correctly, and it’s easy to make mistakes as you deploy subsequent versions.

Amplify introduces an easy way to deploy your application seamlessly using Amplify Console. You only need to point the console at your code repository and it can automagically do the rest. Let’s try this out.

  1. Go to https://console.aws.amazon.com/amplify/.

2. Click “Get Started” under Deploy. In the next screen, select GitHub and follow the steps to authorize access to your GitHub repositories. Click Next.

3. Select the GitHub repo and branch, then click Next:

4. Under build settings, choose to Create a new environment, click “Create new role” and follow the steps to create an Amplify IAM user with the necessary permissions:

5. Review the settings and click “Save and deploy”. The banner at the top of the screen shows the deployment has started:

It takes a few minutes to complete, and then you will see this screen:

At the end of the process, Amplify returns a URL where you can see the deployed application.

Amplify Console also shows screenshots of how your site renders on various mobile resolutions, providing a quick sanity check to ensure you don’t have any glaring layout problems:

Conclusion

For web app developers using single-page applications like Vue or React, Amplify provides an easy way to configure and deploy AWS services, all from a single code repository. We’ve only scratched the surface of its capabilities in this article, but you can see the simplicity of managing key resources using this framework.

In subsequent parts of this series, I will add authorization, multiple environments, and replace the REST API with a real-time GraphQL solution. In the meantime, if you have any questions, let me know in the comments.

Download this project’s code from https://github.com/jbesw/amplify-pollCounter.