DEV Community

Cover image for Movie Cage Match
Paul Chin Jr.
Paul Chin Jr.

Posted on

Movie Cage Match

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. We will also be using OpenJS Architect 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

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.

git clone https://github.com/username/begin-app-project-name.git
cd begin-app-project-name
npm install
Enter fullscreen mode Exit fullscreen mode

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

# app.arc
@app
begin-app

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

@tables
data
  scopeID *String
  dataID **String
  ttl TTL
Enter fullscreen mode Exit fullscreen mode

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.

// 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: '/'
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

curl --request POST \
  --url http://localhost:3333/movies/001 \
  --header 'Content-Type: application/json' \
  --data '{
    "title":"National Treasure"
}'
Enter fullscreen mode Exit fullscreen mode

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.

// 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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

// 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: '/'
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, we can create a function for downvoting a movie.

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: '/'
  }
}
Enter fullscreen mode Exit fullscreen mode

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 and vote for your favorite Nicolas Cage movie. Full source code is here: https://github.com/pchinjr/begin-new-cage-movies

Top comments (0)