Movie Cage Match
Published: 2020-12-22 14:13:06

Today we're going to create a small voting app for my favorite Nicolas Cage movies. We will have multiple endpoints that are deployed directly to AWS with [Begin](https://begin.com). We will also be using [OpenJS Architect](https://arc.codes) as the deployment framework and IAC tool.

## Clone repo and local development

The first step is to click the button to deploy this app to live infrastructure with Begin.

[![Deploy to Begin](https://static.begin.com/deploy-to-begin.svg)](https://begin.com/apps/create?template=https://github.com/pchinjr/begin-new-cage-movies)

Underneath, Begin will create a new GitHub repo to your account to clone to work on locally. Each push to your default branch will trigger a new build and deploy to the `staging` environment. Your CI/CD is already complete!!

When your app deploys, clone the repo, and install the dependencies.

```bash
git clone https://github.com/username/begin-app-project-name.git
cd begin-app-project-name
npm install
```

First, let's look at the `app.arc` file. It gives us an overview of the entire app.

```bash
# app.arc
@app
begin-app

@http
get /
post /movies/:movieID
post /downvote
post /upvote

@tables
data
  scopeID *String
  dataID **String
  ttl TTL
```

In this `.arc` file, we have four routes. Each route is a Lambda function that is triggered by an HTTP method. 

Let's take a look at the POST function, `post-movies-000movieID`, to add a movie to the database.

```javascript
// src/http/post-movies-000movieID/index.js

let data = require('@begin/data')
let arc = require('@architect/functions')

exports.handler = arc.http.async(route)

async function route(req) {
  await data.set({
    table: 'movies',
    key: req.pathParameters.movieID,
    votes: 0,
    title: req.body.title
  })
  return {
    statusCode: 301,
    location: '/'
  }
}
```
Now we can use an HTTP client like Insomnia or a CURL request to POST a movie with a `movieID` and movie `title`. Adding movies to the database first will let us server render the UI needed for the vote buttons.

```bash
curl --request POST \
  --url http://localhost:3333/movies/001 \
  --header 'Content-Type: application/json' \
  --data '{
	"title":"National Treasure"
}'
```
Replace the path param with a unique movieID and a data payload of "title". You can keep adding movies, but I just added a couple to get the functionality working. 

Once we have some objects in the database, let's create the front end. Take a look at the `get-index` route. 

```javascript
// src/http/get-index/index.js

let arc = require('@architect/functions')
let data = require('@begin/data')


exports.handler = async function http(req) {
  let result = await data.get({ table: "movies" })

  let element = result.map(movie).join('')

  function movie(obj) {
    return `
    ${obj.title} : ${obj.key} votes: ${obj.votes}
    <form action="/upvote" method=post>
    <input type=hidden name=movieId value=${obj.key}>
    <button>Upvote</button>
    </form>
    <form action="/downvote" method=post>
    <input type=hidden name=movieId value=${obj.key}>
    <button>Downvote</button>
    </form>
    `
  }

  let html = `<h1> Praise Cage </h1>
   <div>${element}</div>
   `

  return {
    statusCode: 200,
    headers: {
      'cache-control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0',
      'content-type': 'text/html; charset=utf8'
    },
    body: html
  }
}
```
Now we can see the movies that are added, when a user requests the `get-index` page the function will do a lookup for all the movies in the database and generate two form buttons for `downvote` and `upvote` as well as render the current votes and title. You should see a simple UI rendered to the index page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m30wpa5rtgwmtz3zso08.png)

Next, we can write a function for upvoting a movie.

```javascript
// src/http/post-upvote/index.js

let arc = require('@architect/functions')
let data = require('@begin/data')

exports.handler = arc.http.async(route)

async function route(req) {
  let movieId = req.body.movieId
  let table = "movies"
  let incr = await data.incr({ table, key: movieId, prop: "votes" })
  console.log("votes incremented to", incr)
  return {
    statusCode: 301,
    location: '/'
  }
}
```
Finally, we can create a function for downvoting a movie.

```javascript
let arc = require('@architect/functions')
let data = require('@begin/data')

exports.handler = arc.http.async(route)

async function route(req) {
  let movieId = req.body.movieId
  let table = "movies"
  let decr = await data.decr({ table, key: movieId, prop: "votes" })
  console.log("votes decremented to", decr)
  return {
    statusCode: 301,
    location: '/'
  }
}
```
The voting functions use Begin Data client for DynamoDB. Begin data has an API for atomic counters using `.incr` and `.decr` so we know that each of these click operations is going to result in a count down and count up.

## Conclusion
We have a full app with server-side rendered markup hydrated from a database! 
See the [completed app here](https://doge-8yy-staging.begin.app/) and vote for your favorite Nicolas Cage movie. Full source code is here: [https://github.com/pchinjr/begin-new-cage-movies](https://github.com/pchinjr/begin-new-cage-movies)