Skip to content

hmisonne/Serverless_WouldYouRather

Repository files navigation

Build Status

Would You Rather - Serverless App

The goal of this project is to build and deploy a serverless application on AWS. To accomplish this task, I decided to build a "Would You Rather" game with a React frontend and a Node.JS backend using a serverless framework.

I used a CloudFormation Template to deploy:

  • An API gateway
  • Multiple Lambda Functions with proper IAM permissions
  • 2 DynamoDB tables to store records of Users and Questions
  • S3 bucket to store images uploaded by Users

On top of this architecture, I added:

  • A S3 bucket to host my website
  • A registered domain name: hmisonne.com on Route53
  • A CloudFront distribution to accelerate the content delivery
  • A SSL certificates issued from AWS Certificate Manager to secure network communications through https traffic.

This project is also using a fully secured Authentification system through Auth0.

WYR AWS deployment

Functionality of the application

This application allows users to play the would you rather game by answering, creating questions and uploading images.

Screens

This application is currently divided into 4 screens:

Dashboard (Home Screen)

A list of all polls posted is displayed on the home screen. The user can toggle a button to see either the polls that has been answered or unanswered by himself. The option to delete a question or upload a picture will only be available for questions that has been authored by the specific user.

Add picture to a question

The user can upload a picture to a question that he created. Once uploaded, the picture will be shown on the Dashboard.

Poll Details

By clicking on the green button on one specific question from the Dashboard, the user is able to vote provided that he has not done so before. There is 2 options for each poll. Upon voting in a poll, information about how many people voted for one particular option is displayed along with the user's response.

Submit new question

The user can submit a new question by entering 2 options. Once created, the new question will be added to the unanswered questions of the dashboard.

WYR Demo

How to run this application locally on your laptop using the backend server already deployed in AWS

A version of this website is already running on https://hmisonne.com . If you wish to run this application locally follow these instructions:

  1. Update the callbackUrl on the client/config.ts file
export const authConfig = {
  ...        
  callbackUrl: 'https://localhost:3000/callback'
}
  1. and run the following commands:
cd client
npm install
npm run start

The frontend will be running on http://localhost:3000/

How to deploy your own version of the backend application to AWS Serverless

Prerequisites

  • Node.js
  • AWS account
  • Auth0 account

AWS Serverless

  1. Install serverless npm install -g serverless

  2. On your AWS account, set up a new user in IAM named "serverless" with Programmatic access and with AdministratorAccess policy attached and save the access key and secret key.

  3. Configure serverless to use the AWS credentials you just set up: sls config credentials --provider aws --key YOUR_ACCESS_KEY --secret YOUR_SECRET_KEY --profile serverless

Authentification

  1. On your Auth0 account, create a new application with React framework.

  2. On the Settings -> Application URIs -> Allowed Callback URLs insert: http://${hosted_domain}/callback, and Allowed Web Origins: http://${hosted_domain}/

If deployed locally, replace the hosted_domain with localhost:3000

Deploy the backend server to AWS

To deploy this application from the backend folder and generate your apiId, use the following commands: sls deploy -v. Make sure before running this command that you have run: npm install

Run the frontend server

To have the application running on your local machine, edit the client/src/config.ts file to set correct parameters. After running sucessfully sls deploy -v, the endpoints generated by the sls command will be displayed with this configuration https://${apiId}.execute-api.us-east-1.amazonaws.com/dev. Replace apiId with the value

const apiId = 'YOUR_API_ID'

Then replace, the domain and clientId with the values found on your Auth0 account:

export const authConfig = {
  domain: 'YOUR_AUTH0_DOMAIN',
  clientId: 'YOUR_CLIENT_ID',
  callbackUrl: 'http://localhost:3000/callback'
}

Then run the following commands:

cd client
npm install
npm run start

About the DB structure

Database tables

The backend is using DynamoDB databases provisioned on the serverless file by AWS.

QUESTION items

The application store QUESTION items, and each QUESTION item contains the following fields:

  • questionId (string) - a unique id for an item
  • userId (string) - the user id that created this QUESTION item
  • timestamp (string) - date and time when an item was created
  • optionOneText (string) - name of 1st option (e.g. "Win the lottery")
  • optionTwoText (string) - name of 2nd option (e.g. "Have many friends")
  • optionOneVote (array) - List of userId(s) who voted for the 1st option
  • optionTwoVote (array) - List of userId(s) who voted for the 2nd option
  • attachmentUrl (string) (optional) - a URL pointing to an image attached to a QUESTION item

USER information

The application store User information, and each USER item contains the following fields:

  • userId (string) - a unique id for a user
  • answers (object) - with the questionId as attribute and the option selected (optionOne or optionTwo) for each answered question. This allows to filter questions for a specific user by "answering status"

API endpoints

Authentification

  • Auth - this function implement a custom authorizer for API Gateway that has been added to all other functions.

Question API

  • GetQuestions - return all QUESTIONs for a current user . A user id can be extracted from a JWT token that is sent by the frontend. Receive 2 optional parameters:
  • nextKey: Next key to continue scan operation if necessary
  • limit: Maximum number of elements to return

GET https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/questions?limit=2

It returns data that looks like this:

{
    "items": [
        {
            "optionTwoText": "Go West",
            "optionOneText": "Go East",
            "optionTwoVote": [
                "4551045727"
            ],
            "questionId": "286c3233-a2fa-496a-9bc9-9d8cab3ed5f5",
            "attachmentUrl": "https://serverless-wyr-files-dev.s3.amazonaws.com/286c3233-a2fa-496a-9bc9-9d8cab3ed5f5",
            "userId": "4551045727",
            "timestamp": "2020-06-09T21:58:00.982Z"
        },
        {
            "optionTwoText": "Two things",
            "optionOneText": "One thing",
            "optionTwoVote": [
                "1234567891"
            ],
            "questionId": "91ea0481-e7c3-4b39-83a6-edaf3f2ff46e",
            "userId": "4551045727",
            "attachmentUrl": "https://serverless-wyr-files-dev.s3.amazonaws.com/91ea0481-e7c3-4b39-83a6-edaf3f2ff46e",
            "optionOneVote": [
                "4551045727",
                "1234567891"
            ],
            "timestamp": "2020-06-06T01:47:03.364Z"
        }
    ]
}
  • GetQuestion - return a single QUESTION item by userId and by questionId.

GET https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/users/{userId}/questions/{questionId}

It returns data that looks like this:

{
    "item": {
        "optionTwoText": "Two things",
        "optionOneText": "One thing",
        "optionTwoVote": [
            "4551045727",
            "5588959907"
        ],
        "questionId": "91ea0481-e7c3-4b39-83a6-edaf3f2ff46e",
        "attachmentUrl": "https://serverless-wyr-files-dev.s3.amazonaws.com/91ea0481-e7c3-4b39-83a6-edaf3f2ff46e",
        "userId": "4551045727",
        "optionOneVote": [
            "4551045123",
        ],
        "timestamp": "2020-06-06T01:47:03.364Z"
    }
}
  • CreateQuestion - create a new QUESTION for a current user. A shape of data send by a client application to this function can be found in the requests/CreateQuestionRequest.ts file

POST https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/questions

body: {
	"optionOneText": "Go East",
	"optionTwoText": "Go West"
}

It returns a new QUESTION item that looks like this:

{
    "item": {
        "optionTwoText": "Go West",
        "optionOneText": "Go East",
        "questionId": "286c3233-a2fa-496a-9bc9-9d8cab3ed5f5",
        "userId": "4551045727",
        "timestamp": "2020-06-09T21:58:00.982Z"
    }
}
  • SubmitVote - update a QUESTION item with the vote submitted by a current user. It uses the userId which is here the creator of the question (not the current user) and the questionId in the path to identify a question.

PATCH https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/users/{userId}/questions/{questionId}

It receives an object that contains that the option selected:

{
  "optionSelected": "OptionOne",
}

The id of the current user (responder of the poll) is extracted from a JWT token passed by the client.

This function will update the QUESTION item as well as the USER item:

  • It will append the userId (responder id) to the optionSelected parameter (optionOneVote/optionTwoVote) of the QUESTION item:
{
    ...
    "optionOneVote": [..., "4551045123"]
}
  • It will add a new element to the answers parameter of the USER item with the questionId as key and optionSelected as value.
{
    ...
    "answers": {
        ..., 
        "286c3233-a2fa-496a-9bc9-9d8cab3ed5f5": "optionOne"
    }
}

It returns an empty body.

  • DeleteQuestion - delete a QUESTION item created by a current user. Expects an id of a QUESTION item to remove.

DELETE https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/questions/{questionId}

It returns an empty body.

  • GenerateUploadUrl - it is used to add a picture to a QUESTION item.

POST https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/questions/{questionId}/attachment

It returns a JSON object that looks like this:

{
  "uploadUrl": "https://{{s3-bucket-name.s3}}.eu-west-2.amazonaws.com/{{image-name}}.png"
}

This pre-signed URL is then used to upload an attachment file to a s3 bucket for a QUESTION item. It then updates the QUESTION item by adding an attachmentUrl key with the s3 location of the item as value {attachmentUrl: "https://${bucketName}.s3.amazonaws.com/${questionId}"}

User API

  • createUser - add a new user to the database:

POST https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/users

it returns a JSON object that looks like this:

{
    "user": [
        {
            "answers": {},
            "userId": "5588959907"
        }
    ]
}
  • getResponsesByUser - returns user information for a current user using the JWT token that is sent by the frontend.

GET https://{{apiId}}.execute-api.us-east-1.amazonaws.com/dev/users

{
    "user": [
        {
            "answers": {
                "286c3233-a2fa-496a-9bc9-9d8cab3ed5f5": "optionOne",
                "286c3233-a2fa-496a-9bc9-9d8cab3ed5f5": "optionTwo"
                },
            "userId": "5588959907"
        }
    ]
}

All functions are already connected to appropriate events from API Gateway.

An id of a user can be extracted from a JWT token passed by a client.

The serverless.yml file includes all of these functions as well as a DynamoDB table and a S3 bucket in the resources.

Test

To test the endpoints of this application download the postman collection: ServerlessWYR.postman_collection.json

CI/CD pipeline

I used Travis to automically deploy and update the application. Each time a new commit is pushed to the master branch, a new build is triggered. A commit pushed to the development branch won't trigger a new deployment, which allows to integrate changes without affecting the production environment.

About

AWS Serverless App using React, TypeScript, DynamoDb and S3

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published