diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ad46b30
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# next.js build output
+.next
diff --git a/README.md b/README.md
index 96cb7de..4108b85 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,102 @@
-# react-serverless
-Tutorial that guides the user in building a full stack tutorial
+# Full Stack Tutorial
+## Build a Todo App with Serverless & React
+
+Usually, React tutorials focus on getting a frontend running on your local machine. This React tutorial leverages serverless to build a complete todo list app. By the end, we will have built both a frontend and a backend, running in a scalable production-ready cloud environment. No credit card required.
+
+Learn more about serverless [here](https://blog.binaris.com/from-servers-to-serverless/). Learn more about Binaris [here](https://binaris.com/).
+
+## Steps
+
+You can **fast forward** and start at any step. Each step contains the necessary code and instructions to catch up with previous steps.
+
+ 1. [Develop a Frontend on Your Local Machine](./tutorial_sections/develop_frontend.md) :clock1: 30 minutes
+
+ 1. [Serve the Frontend from a Function](./tutorial_sections/serve_frontend.md) :clock1: 10 minutes
+
+ 1. [Set Up a Redis Datastore](./tutorial_sections/setup_redis.md) :clock1: 5 minutes
+
+ 1. [Build a CRUD Backend with Functions](./tutorial_sections/build_a_crud.md) :clock1: 30 minutes
+
+ 1. [Call the Backend Functions from the React Frontend](./tutorial_sections/connect_everything.md) :clock1: 10 minutes
+
+
+Or, follow the simple steps below and skip it all
+
+Skip to "Just do it for me"
+
+ Download [assets](https://github.com/binaris/react-serverless/archive/connect-everything.zip) and get started
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+ ### Setup Redis
+
+ If you already have a Redis account, you can use either a new or pre-existing Redis instance from your account. Otherwise, you have to go through the account and instance creation flow described [here](./setup_redis.md).
+
+ ```bash
+ $ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+ ```
+
+ ### Setup everything
+
+ ```bash
+ $ cd backend
+ $ npm install
+ $ npm run deploy
+ $ cd ../frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your Account ID will always be a unique number, 10 digits in length.
+
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/1234******/public_serve_todo",
+ "dependencies": {
+ ```
+
+ Export the root endpoint environment variable (using your personal `ACCOUNT_ID`)
+
+ ```bash
+ $ export REACT_APP_BINARIS_ROOT_ENDPOINT="https://run.binaris.com/v2/run/1234******/"
+ $ cd serve_todo
+ $ npm install
+ $ cd ../
+ $ npm install && npm run build && npm run deploy
+ ```
+
+ Navigate to the URL provided in the output dialog to view your app.
+
+
+
+
+
+## Architecture
+
+Our app has four parts:
+1. React frontend running in the browser
+1. A serverless function serving the frontend
+1. Serverless CRUD backend running on Binaris
+1. Data stored in Redis
+
+
+
+## Dependencies
+
+* NodeJS
+* npm
diff --git a/arch.png b/arch.png
new file mode 100644
index 0000000..37e5423
Binary files /dev/null and b/arch.png differ
diff --git a/res/redis1.png b/res/redis1.png
new file mode 100644
index 0000000..efc24f0
Binary files /dev/null and b/res/redis1.png differ
diff --git a/res/redis2.png b/res/redis2.png
new file mode 100644
index 0000000..dbde3da
Binary files /dev/null and b/res/redis2.png differ
diff --git a/res/redis3.png b/res/redis3.png
new file mode 100644
index 0000000..69ffc1b
Binary files /dev/null and b/res/redis3.png differ
diff --git a/res/redis4.png b/res/redis4.png
new file mode 100644
index 0000000..a0820dc
Binary files /dev/null and b/res/redis4.png differ
diff --git a/tutorial_sections/build_a_crud.md b/tutorial_sections/build_a_crud.md
new file mode 100644
index 0000000..4f6f4ee
--- /dev/null
+++ b/tutorial_sections/build_a_crud.md
@@ -0,0 +1,756 @@
+## Build a CRUD Backend with Functions
+
+[< Setup a Redis Data Store](./setup_redis.md)
+
+Skip to "Build a CRUD Backend with Functions"
+
+ Download [assets](https://github.com/binaris/react-serverless/archive/serve-a-frontend.zip) and get started
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+
+ ### Setup Redis
+
+ If you already have a Redis account, you can use either a new or pre-existing Redis instance from your account. Otherwise, you have to go through the account and instance creation flow described [here](./setup_redis.md).
+
+ ```bash
+ $ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+ ```
+
+ ### Setup the Frontend
+
+ ```bash
+ $ cd frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your Account ID will always be a unique number, 10 digits in length.
+
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/23232*****/public_serve_todo",
+ "dependencies": {
+ ```
+
+
+ ```bash
+ $ npm install
+ $ cd serve_todo
+ $ npm install
+ ```
+
+ ### To verify that you've successfully caught up...
+
+ ```bash
+ $ cd ../
+ $ npm run build
+ $ npm run deploy
+ ```
+
+ And navigate to the URL provided in the output dialog.
+
+
+
+## Table of Contents
+
+1. [Create the Create Function](#backend-create-create)
+1. [Use Environment Variables](#backend-env-vars)
+1. [Create the Read, Update, and Delete Functions](#backend-rud-functions)
+1. [Add CORS support to our Backend Functions](backend-cors-functions)
+
+
+
+### Initialize project
+
+We will create the backend directory, create the Binaris project, and create the Redis functions file.
+
+First, ensure that you are out of the `frontend` directory. If your frontend directory is currently your working directory, first do...
+
+```bash
+$ pwd
+ /Users/ubuntu/todo/frontend
+$ cd ..
+```
+
+Then...
+
+```bash
+$ mkdir backend
+$ cd backend
+$ bn create node8 public_create_endpoint --executionModel concurrent
+```
+
+We will also rename our generated `function.js` file, since this will contain all four of our CRUD functions.
+
+```bash
+$ mv ./function.js functions.js
+```
+
+### Update the `binaris.yml` file
+
+We'll update the `entrypoint` field of the function so it maps to the new function location.
+
+```diff
+> backend/binaris.yml
+---
+ functions:
+ public_create_endpoint:
+ file: functions.js
+- entrypoint: handler
++ entrypoint: create_endpoint
+ runtime: node8
+ executionModel: concurrent
+```
+
+Time to write our Redis CRUD code.
+
+### Write the Create Code
+
+[Just Show Me the Code](#backend-create-code)
+
+Let's start by adding the CREATE functionality to redisConnection.js.
+
+First we install our dependencies.
+
+```bash
+$ npm init -y
+$ npm install ioredis uuid
+```
+
+First, we will add our dependencies, which include ioredis and uuid.
+
+```diff
+> backend/functions.js
+---
++'use strict';
++
++const uuid = require('uuid/v4');
++const Redis = require('ioredis');
+
+ exports.handler = async (body, context) => {
+```
+
+Let's also clear out the generated boilerplate code. We are going to have all of our handlers in one file, so we will rename the handler function. We also will not have to use the context argument, so we will be removing that as well.
+
+```diff
+> backend/functions.js
+---
+-exports.handler = async (body, context) => {
++exports.createEndpoint = async (body) => {
+- const name = context.request.query.name || body.name || 'World';
+- return `Hello ${name}!`;
+ }
+```
+
+Next, we will be creating our hash key for Redis, and setting up our Redis client.
+
+```diff
+> backend/functions.js
+---
+ const Redis = require('ioredis');
++
++const HASH_KEY = 'todoList';
++
++const client = new Redis({
++ host: ,
++ port: ,
++ password: ,
++});
++
+exports.createEndpoint = async (body) => {
+```
+
+Now that we have done all the setup, we can write our create function. This function will generate a unique key for the message in the hash set, insert the message with the key, and return the key value pair.
+
+```diff
+> backend/functions.js
+---
+ password: ,
+ });
++
+ exports.createEndpoint = async (body) => {
++ const key = uuid();
++ await hSet(HASH_KEY, key, body.message);
++ return { [key]: body.message };
+ }
+```
+
+We will also add in a helper function that will validate our body parameters for us. By default, Binaris returns an empty dict, so we just need to check for our expected parameters.
+
+```diff
+> backend/functions.js
+---
+ password: ,
+ });
+
++function validateBody(body, ...fields) {
++ for(const field of fields) {
++ if (!body[field]) {
++ throw new Error(`Missing request body parameter: ${field}.`);
++ }
++ }
++}
++
+ exports.createEndpoint = async (body) => {
+```
+
+Now let's implement our body validation function in our create function. The only parameter we need for create is the message, so we will pass that into the `validateBody` method.
+
+```diff
+> backend/functions.js
+---
+ exports.createEndpoint = async (body) => {
++ validateBody(body, 'message');
+ const key = uuid();
+ await hSet(HASH_KEY, key, body.message);
+ return { [key]: body.message };
+ }
+```
+
+With our first function written, it's time to deploy and test it!
+
+```bash
+$ bn deploy public_create_endpoint
+```
+
+> Note: The invocation methods that will print out after deployment will not include the required `data` field. To invoke successfully, use one of the following methods:
+>```bash
+> $ bn invoke public_create_endpoint --data '{"message": "test"}'
+> $ curl https://run.binaris.com/v2/run/{your_account_id}/public_create_endpoint --data '{"message": "test"}'
+>```
+
+
+
+ Current state of binaris.yml
+
+```yml
+functions:
+ public_create_endpoint:
+ file: functions.js
+ entrypoint: create_endpoint
+ runtime: node8
+ executionModel: concurrent
+```
+
+
+
+ Current state of functions.js
+
+```JavaScript
+`use strict`;
+
+const uuid = require('uuid/v4');
+const Redis = require('ioredis');
+
+const HASH_KEY = 'todoList';
+
+const client = new Redis({
+ host: ,
+ port: ,
+ password: ,
+});
+
+function validateBody(body, ...fields) {
+ for (const field of fields) {
+ if (!body[field]) {
+ throw new Error(`Missing request body parameter: ${field}.`);
+ }
+ }
+}
+
+exports.createEndpoint = async (body, context) => {
+ validateBody(body, 'message');
+ const key = uuid();
+ await hSet(HASH_KEY, key, body.message);
+ return { [key]: body.message };
+};
+```
+
+
+
+
+
+### Use Environment Variables
+
+Now that our function is working, let's add our Redis secrets to our environment variables so that we can access them from our `binaris.yml` file. (that way, if you decide to commit this code anywhere, your secrets are safe!).
+
+```bash
+$ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+```
+
+With our Redis secrets in our environment variables, we can add them to our `binaris.yml` file. We will also alias them as `COMMON`, since we will need to use them with our other functions later on. For more information on how to use yaml aliases, see [this link](https://github.com/cyklo/Bukkit-OtherBlocks/wiki/Aliases-(advanced-YAML-usage)).
+
+```diff
+> backend/binaris.yml
+---
+functions:
+ public_create_endpoint:
+ file: src/create.js
+ entrypoint: handler
+ runtime: node8
+ executionModel: concurrent
++ env:
++ <<: &COMMON
++ REDIS_HOST:
++ REDIS_PORT:
++ REDIS_PASSWORD:
+```
+
+With the constants in our `binaris.yml`, we can reference them in our code.
+
+```diff
+> backend/functions.js
+---
+ const redis = require('redis');
+
+ const HASH_KEY = 'todoList';
+
++const {
++ REDIS_HOST: host,
++ REDIS_PORT: port,
++ REDIS_PASSWORD: password,
++} = process.env;
++
+```
+
+Now that we have the environment variable constants, let's use them in the redis client creation.
+
+```diff
+> backend/functions.js
+---
+} = process.env;
+
+ const client = redis.createClient({
+- host: ,
+- port: ,
+- password: ,
++ host,
++ port,
++ password,
+ });
+```
+
+Time to redeploy our create function to propogate these changes.
+
+```bash
+$ bn deploy public_create_endpoint
+```
+
+
+## Create the Read, Update and Delete Functions
+
+[Just Show Me the Code](#backend-final-code)
+
+Time to create our other three functions. First, let's update our `binaris.yml` file to add the necessary functions, handlers, and environment variables.
+
+```diff
+> backend/binaris.yml
+---
+ functions:
+ public_create_endpoint:
+ file: functions.js
+ entrypoint: create_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: &COMMON
+ REDIS_HOST:
+ REDIS_PORT:
+ REDIS_PASSWORD:
++ public_read_endpoint:
++ file: functions.js
++ entrypoint: read_endpoint
++ runtime: node8
++ executionModel: concurrent
++ env:
++ <<: *COMMON
++ public_update_endpoint:
++ file: functions.js
++ entrypoint: update_endpoint
++ runtime: node8
++ executionModel: concurrent
++ env:
++ <<: *COMMON
++ public_delete_endpoint:
++ file: functions.js
++ entrypoint: delete_endpoint
++ runtime: node8
++ executionModel: concurrent
++ env:
++ <<: *COMMON
+```
+
+With the setup in our `binaris.yml` complete, let's add the function handlers in our `functions.js` file.
+
+```diff
+> backend/functions.js
+---
+ exports.createEndpoint = async (body) => {
+ validateBody(body, 'message');
+ const key = uuid();
+ await client.hset(HASH_KEY, key, body.message);
+ return { [key]: body.message };
+ };
++
++exports.readEndpoint = async () => {
++ const redisDict = await client.hgetall(HASH_KEY);
++ return redisDict;
++};
++
++exports.updateEndpoint = async (body) => {
++ validateBody(body, 'message', 'id');
++ await client.hset(HASH_KEY, body.id, body.message);
++ return { [body.id]: body.message };
++};
++
++exports.deleteEndpoint = async (body) => {
++ validateBody(body, 'id');
++ client.hdel(HASH_KEY, body.id);
++};
+```
+
+With our handler complete, we can now deploy our new binaris functions.
+
+```bash
+$ bn deploy public_read_endpoint
+$ bn deploy public_update_endpoint
+$ bn deploy public_delete_endpoint
+```
+
+
+
+ Current State of binaris.yml
+
+```yml
+functions:
+ public_create_endpoint:
+ file: functions.js
+ entrypoint: create_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: &COMMON
+ REDIS_HOST:
+ REDIS_PORT:
+ REDIS_PASSWORD:
+ public_read_endpoint:
+ file: functions.js
+ entrypoint: read_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: *COMMON
+ public_update_endpoint:
+ file: functions.js
+ entrypoint: update_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: *COMMON
+ public_delete_endpoint:
+ file: functions.js
+ entrypoint: delete_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: *COMMON
+```
+.p
+
+
+ Current State of functions.js
+
+```JavaScript
+`use strict`;
+
+const uuid = require('uuid/v4');
+const Redis = require('ioredis');
+
+const HASH_KEY = 'todoList';
+
+const {
+ REDIS_HOST: host,
+ REDIS_PORT: port,
+ REDIS_PASSWORD: password,
+} = process.env;
+
+const client = new Redis({
+ host,
+ port,
+ password,
+});
+
+function validateBody(body, ...fields) {
+ for (const field of fields) {
+ if (!body[field]) {
+ throw new Error(`Missing request body parameter: ${field}.`);
+ }
+ }
+}
+
+exports.createEndpoint = async (body) => {
+ validateBody(body, 'message');
+ const key = uuid();
+ await hSet(HASH_KEY, key, body.message);
+ return { [key]: body.message };
+};
+
+exports.readEndpoint = async () => {
+ const redisDict = await client.hgetall(HASH_KEY);
+ return redisDict;
+};
+
+exports.updateEndpoint = async (body) => {
+ validateBody(body, 'message', 'id');
+ await client.hset(HASH_KEY, body.id, body.message);
+ return { [body.id]: body.message };
+};
+
+exports.deleteEndpoint = async (body) => {
+ validateBody(body, 'id');
+ client.hdel(HASH_KEY, body.id);
+};
+```
+
+
+
+
+### Add CORS support to our Backend Functions
+
+[Just Show Me The Code](#backend-cors-code)
+
+One last step with the backend; our frontend functions will be using [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) to communicate with the backend, so we need to support CORS requests and responses. One specific thing we will need to support is [CORS Preflight Requests](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request). Additionally, our responses will have to support CORS Headers.
+
+Let's start by adding the function that will be wrapping our return values in HTTPResponses, and adding the CORS headers that our frontend will be expecting from us. This function takes in the context and the intended response body, and returns a CORS-compliant response.
+
+```diff
+> backend/functions.js
+---
+ throw new Error(`Missing request body parameter: ${field}.`);
+ }
+ });
+ }
++
++function responseContent(context, responseBody) {
++ const response = {
++ statusCode: 200,
++ headers: {
++ 'Access-Control-Allow-Origin': '*',
++ 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
++ },
++ };
++ if (body !== undefined) {
++ response.headers['Content-Type'] = 'application/json';
++ response.body = JSON.stringify(responseBody);
++ }
++ return new context.HTTPResponse(response);
++}
+```
+
+Next, we create the function that will handle the CORS preflight requests. This function will:
+1. Intercept all requests that are sent to the specific CRUD function
+1. Check to see if it is a preflight request
+1. If so, return a blank response
+1. Otherwise, return the wrapped response from the designated CRUD function.
+
+```diff
+> backend/functions.js
+---
++ response.body = JSON.stringify(responseBody);
++ }
++ return new context.HTTPResponse(response);
++}
++
++function handleCORS(handler) {
++ return async (body, context) => {
++ if (context.request.method === 'OPTIONS') {
++ return responseContent(context);
++ }
++ const result = await handler(body);
++ return responseContent(context, result);
++ };
++}
+```
+
+Now that our CORS preflight-handling [decorator](https://www.sitepoint.com/javascript-decorators-what-they-are/) is written, we can add it to each of our function handlers.
+
+```diff
+> backend/functions.js
+---
+-exports.createEndpoint = async (body) => {
++exports.createEndpoint = handleCORS(async (body) => {
+ validateBody(body, 'message');
+ const key = uuid();
+ await client.hset(HASH_KEY, key, body.message);
+ return { [key]: body.message };
+-}
++});
+
+-exports.readEndpoint = async (body) => {
++exports.readEndpoint = handleCORS(async (body) => {
+ const redisDict = await client.hgetall(HASH_KEY);
+ return redisDict;
+-}
++});
+
+-exports.updateEndpoint = async(body) => {
++exports.updateEndpoint = handleCORS(async (body) => {
+ validateBody(body, 'message', 'id');
+ await client.hset(HASH_KEY, body.id, body.message);
+ return { [body.id]: body.message };
+-}
++});
+
+-exports.deleteEndpoint = async (body) => {
++exports.deleteEndpoint = handleCORS(async (body) => {
+ validateBody(body, 'id');
+ client.hdel(HASH_KEY, body.id);
+-}
++});
+```
+
+
+
+ Final State of binaris.yml
+
+```yml
+functions:
+ public_create_endpoint:
+ file: functions.js
+ entrypoint: create_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: &COMMON
+ REDIS_HOST:
+ REDIS_PORT:
+ REDIS_PASSWORD:
+ public_read_endpoint:
+ file: functions.js
+ entrypoint: read_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: *COMMON
+ public_update_endpoint:
+ file: functions.js
+ entrypoint: update_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: *COMMON
+ public_delete_endpoint:
+ file: functions.js
+ entrypoint: delete_endpoint
+ runtime: node8
+ executionModel: concurrent
+ env:
+ <<: *COMMON
+```
+.p
+
+
+ Final State of functions.js
+
+```JavaScript
+'use strict';
+
+const uuid = require('uuid/v4');
+const Redis = require('ioredis');
+
+const HASH_KEY = 'todoList';
+
+const {
+ REDIS_HOST: host,
+ REDIS_PORT: port,
+ REDIS_PASSWORD: password,
+} = process.env;
+
+const client = new Redis({
+ host,
+ port,
+ password,
+});
+
+function validateBody(body, ...fields) {
+ for (const field of fields) {
+ if (!body[field]) {
+ throw new Error(`Missing request body parameter: ${fields}.`);
+ }
+ }
+}
+
+function responseContent(context, responseBody) {
+ const response = {
+ statusCode: 200,
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
+ },
+ };
+ if (responseBody !== undefined) {
+ response.headers['Content-Type'] = 'application/json';
+ response.body = JSON.stringify(responseBody);
+ }
+ return new context.HTTPResponse(response);
+}
+
+function handleCORS(handler) {
+ return async (body, context) => {
+ if (context.request.method === 'OPTIONS') {
+ return responseContent(context);
+ }
+ const result = await handler(body);
+ return responseContent(context, result);
+ };
+}
+
+exports.createEndpoint = handleCORS(async (body) => {
+ validateBody(body, 'message');
+ const key = uuid();
+ await client.hset(HASH_KEY, key, body.message);
+ return { [key]: body.message };
+});
+
+exports.readEndpoint = handleCORS(async () => {
+ const redisDict = await client.hgetall(HASH_KEY);
+ return redisDict;
+});
+
+exports.updateEndpoint = handleCORS(async (body) => {
+ validateBody(body, 'message', 'id');
+ await client.hset(HASH_KEY, body.id, body.message);
+ return { [body.id]: body.message };
+});
+
+exports.deleteEndpoint = handleCORS(async (body) => {
+ validateBody(body, 'id');
+ client.hdel(HASH_KEY, body.id);
+});
+```
+
+
+
+Finally, we redeploy and we are done with the backend!
+
+```bash
+$ npm run deploy
+```
+
+[Call the Backend Functions from the React Frontend >](./connect_everything.md)
diff --git a/tutorial_sections/catchup.md b/tutorial_sections/catchup.md
new file mode 100644
index 0000000..e887e06
--- /dev/null
+++ b/tutorial_sections/catchup.md
@@ -0,0 +1,257 @@
+Skip to "Serve the Frontend from a Function"
+
+ ### Setup the frontend
+
+ ```bash
+ $ cd frontend
+ $ npm install
+ ```
+
+ ### To verify that you've successfully caught up...
+
+ ```bash
+ $ npm run start
+ ```
+
+
+
+
+
+Skip to "Set Up a Redis Datastore"
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+ ### Setup the Frontend
+
+ ```bash
+ $ cd frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your account ID will always be a unique number, 10 digits in length.
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/23232*****/public_serve_todo",
+ "dependencies": {
+ ```
+
+ And then run the following commands
+
+ ```bash
+ $ npm install
+ $ cd serve_todo
+ $ npm install
+ ```
+
+ ### To verify that you've successfully caught up...
+
+ ```bash
+ $ cd ../
+ $ npm run build && npm run deploy
+ ```
+
+
+
+
+Skip to "Build a CRUD Backend with Functions"
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+
+ ### Setup Redis
+
+ If you already have a Redis account, you can use either a new or pre-existing Redis instance from your account. Otherwise, you have to go through the account and instance creation flow described [here](./setup_redis.md).
+
+ ```bash
+ $ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+ ```
+
+ ### Setup the Frontend
+
+ ```bash
+ $ cd frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your Account ID will always be a unique number, 10 digits in length.
+
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/23232*****/public_serve_todo",
+ "dependencies": {
+ ```
+
+
+ ```bash
+ $ npm install
+ $ cd serve_todo
+ $ npm install
+ ```
+
+ ### To verify that you've successfully caught up...
+
+ ```bash
+ $ cd ../
+ $ npm run build
+ $ npm run deploy
+ ```
+
+ And navigate to the URL provided in the output dialog.
+
+
+
+Skip to "Call the Backend Functions from the React Frontend"
+
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+ ### Setup Redis
+
+ If you already have a Redis account, you can use either a new or pre-existing Redis instance from your account. Otherwise, you have to go through the account and instance creation flow described [here](./setup_redis.md).
+
+ ```bash
+ $ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+ ```
+
+ ### Setup the Frontend and Backend
+
+ ```bash
+ $ cd backend
+ $ npm install
+ $ npm run deploy
+ $ cd ../frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your Account ID will always be a unique number, 10 digits in length.
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/23232*****/public_serve_todo",
+ "dependencies": {
+ ```
+
+ ```bash
+ $ cd serve_todo
+ $ npm install
+ $ cd ../
+ $ npm install
+ ```
+
+ ### To verify that you've successfully caught up...
+
+ ```bash
+ $ npm run build && npm run deploy
+ ```
+
+ And navigate to the URL provided in the output dialog.
+
+
+
+Skip to "Just do it for me"
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+ ### Setup Redis
+
+ If you already have a Redis account, you can use either a new or pre-existing Redis instance from your account. Otherwise, you have to go through the account and instance creation flow described [here](./setup_redis.md).
+
+ ```bash
+ $ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+ ```
+
+ ### Setup everything
+
+ ```bash
+ $ cd backend
+ $ npm install
+ $ npm run deploy
+ $ cd ../frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your Account ID will always be a unique number, 10 digits in length.
+
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/1234******/public_serve_todo",
+ "dependencies": {
+ ```
+
+ Export the root endpoint environment variable (using your personal `ACCOUNT_ID`)
+
+ ```bash
+ $ export REACT_APP_BINARIS_ROOT_ENDPOINT="https://run.binaris.com/v2/run/1234******/"
+ $ cd serve_todo
+ $ npm install
+ $ cd ../
+ $ npm install && npm run build && npm run deploy
+ ```
+
+ Navigate to the URL provided in the output dialog to view your app.
+
+
+
\ No newline at end of file
diff --git a/tutorial_sections/connect_everything.md b/tutorial_sections/connect_everything.md
new file mode 100644
index 0000000..50a7b8f
--- /dev/null
+++ b/tutorial_sections/connect_everything.md
@@ -0,0 +1,438 @@
+# Call the Backend Functions From the React Frontend
+
+[< Build a CRUD Backend with Functions](./build_a_crud.md)
+
+Skip to "Call the Backend Functions from the React Frontend"
+
+ Download [assets](https://github.com/binaris/react-serverless/archive/build-a-crud.zip) and get started
+
+ ### Setup Your Binaris Environment
+
+ For the next section you will need a Binaris account, if you already have one skip the following four steps.
+
+ 1. Visit https://binaris.com/try
+ 1. Follow the instructions and create your new Binaris account
+ 1. Install the CLI via `npm`
+ ```bash
+ npm install binaris -g
+ ```
+ 1. Use `bn login` to authenticate with your newly created Binaris account
+ 1. (Optional) visit our [getting started](https://dev.binaris.com/tutorials/nodejs/getting-started/) page to learn the basics
+
+ ### Setup Redis
+
+ If you already have a Redis account, you can use either a new or pre-existing Redis instance from your account. Otherwise, you have to go through the account and instance creation flow described [here](./setup_redis.md).
+
+ ```bash
+ $ export REDIS_HOST= REDIS_PORT= REDIS_PASSWORD=
+ ```
+
+ ### Setup the Frontend and Backend
+
+ ```bash
+ $ cd backend
+ $ npm install
+ $ npm run deploy
+ $ cd ../frontend
+ ```
+
+ Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace `` with your specific Binaris account ID. Assuming you successfully ran `bn login`, your account ID can be found in `~/.binaris.yml`.
+
+ > Note: Your Account ID will always be a unique number, 10 digits in length.
+
+ ```diff
+ > frontend/package.json
+ ---
+ "private": true,
+ -"homepage": "https://run.binaris.com/v2/run//public_serve_todo",
+ +"homepage": "https://run.binaris.com/v2/run/23232*****/public_serve_todo",
+ "dependencies": {
+ ```
+
+ ```bash
+ $ cd serve_todo
+ $ npm install
+ $ cd ../
+ $ npm install
+ ```
+
+ ### To verify that you've successfully caught up...
+
+ ```bash
+ $ npm run build && npm run deploy
+ ```
+
+ And navigate to the URL provided in the output dialog.
+
+
+
+
+
+## Table of Contents
+1. [Define our Backend Interface](#backend-interface)
+1. [Connect the Things](#connect-the-things)
+
+Now that the backend and frontend are finished, the only task left is to hook them up. We know that the frontend will need to communicate with the backend, and because we adopted the `CRUD` paradigm, our interface should be relatively straightforward.
+
+
+
+
+### Define our Backend Interface
+
+[Just Show Me the Code](#backend-interface-code)
+
+It's good practice to separate responsibilities, especially when frontend and backend code is involved. So let's make a new file in `frontend/src/` named `BinarisAPI.js`, which will be the interface between the frontend and backend logic.
+
+We need to make some requests, but how do we know where to make the requests? This answer will come later but for now we can assume that our `BinarisAPI` is passed the root account endpoint. The root account endpoint is the portion of the endpoint that doesn't contain function specific information.
+
+```
+// function specific BAD
+https://run.binaris.com/v2/run/1234*****/fooFunc
+
+// account specific GOOD
+https://run.binaris.com/v2/run/1234*****/
+
+```
+
+With that in mind, let's make the class BinarisAPI with a constructor that takes in a single `rootEndpoint` argument.
+
+```diff
+> src/BinarisAPI.js
+---
++class BinarisAPI {
++ constructor(rootEndpoint) {
++ }
++}
++
++export default BinarisAPI;
+```
+
+We know that, by the end, we'll need to handle four different endpoints (one for each `CRUD` operation), fortunately the endpoint for each function is deterministic, so we can already define them. We'll use the `url-join` package from npm to join our urls just to play things on the safe side.
+
+```diff
+> src/BinarisAPI.js
+---
++import urljoin from 'url-join';
++
+ class BinarisAPI {
+ constructor(rootEndpoint) {
++ this.createEndpoint = urljoin(rootEndpoint, 'public_create_endpoint');
++ this.readEndpoint = urljoin(rootEndpoint, 'public_read_endpoint');
++ this.updateEndpoint = urljoin(rootEndpoint, 'public_update_endpoint');
++ this.deleteEndpoint = urljoin(rootEndpoint, 'public_delete_endpoint');
+ }
+```
+
+We've defined all our endpoints, so now it's time to start thinking about the requests that need to be sent. Although each CRUD operation is using a different endpoint, they do share some similarities. For example, all requests will be POST requests and must have `CORS` enabled
+
+Knowing this, let's write a generic request handler that can be used for all of the CRUD operations.
+
+```diff
+> src/BinarisAPI.js
+---
+ import urljoin from 'url-join';
+
++function CORSOptions(itemData) {
++ const options = {
++ method: 'POST',
++ mode: 'cors',
++ };
++ if (itemData) {
++ options.body = JSON.stringify(itemData);
++ options.headers = { 'Content-Type': 'application/json' };
++ }
++ return options;
++}
+
+ class BinarisAPI {
+```
+
+We define the function outside of the class scope because we don't want it being called directly from external users.
+
+`reqWithCORS` covers all our bases in terms of CRUD operations, so all that's left is to actually utilize it. We'll accomplish this by wrapping each `CRUD` operation in a class method, while adhering to the API defined in the backend tutorial.
+
+```diff
+> src/BinarisAPI.js
+---
+ this.updateEndpoint = urljoin(rootEndpoint, 'public_update_endpoint');
+ this.deleteEndpoint = urljoin(rootEndpoint, 'public_delete_endpoint');
+ }
+
++ async createItem(item) {
++ const res = await fetch(this.createEndpoint, CORSOptions({ message: item }));
++ return res.json();
++ }
++
++ async readAllItems() {
++ const res = await fetch(this.readEndpoint, CORSOptions());
++ return res.json();
++ }
++
++ async updateItem(itemID, item) {
++ const mergeData = {
++ message: item,
++ id: itemID,
++ };
++ const res = await fetch(this.updateEndpoint, CORSOptions(mergeData));
++ return res.json();
++ }
++
++ async deleteItem(itemID) {
++ await fetch(this.deleteEndpoint, CORSOptions({ id: itemID }));
++ }
+ }
+
+ export default BinarisAPI;
+```
+
+
+
+ Final state of BinarisAPI.js
+
+```JavaScript
+import urljoin from 'url-join';
+
+function CORSOptions(itemData) {
+ const options = {
+ method: 'POST',
+ mode: 'cors',
+ };
+ if (itemData) {
+ options.body = JSON.stringify(itemData);
+ options.headers = { 'Content-Type': 'application/json' };
+ }
+ return options;
+}
+
+class BinarisAPI {
+ constructor(rootEndpoint) {
+ this.createEndpoint = urljoin(rootEndpoint, 'public_create_endpoint');
+ this.readEndpoint = urljoin(rootEndpoint, 'public_read_endpoint');
+ this.updateEndpoint = urljoin(rootEndpoint, 'public_update_endpoint');
+ this.deleteEndpoint = urljoin(rootEndpoint, 'public_delete_endpoint');
+ }
+
+ async createItem(item) {
+ const res = await fetch(this.createEndpoint, CORSOptions({ message: item }));
+ return res.json();
+ }
+
+ async readAllItems() {
+ const res = await fetch(this.readEndpoint, CORSOptions());
+ return res.json();
+ }
+
+ async updateItem(itemID, item) {
+ const mergeData = {
+ message: item,
+ id: itemID,
+ };
+ const res = await fetch(this.updateEndpoint, CORSOptions(mergeData));
+ return res.json();
+ }
+
+ async deleteItem(itemID) {
+ await fetch(this.deleteEndpoint, CORSOptions({ id: itemID }));
+ }
+}
+
+export default BinarisAPI;
+```
+
+
+
+
+
+
+### Connect the Things
+
+[Just Show Me the Code](#connect-the-things-code)
+
+Our final task is to integrate the API into our `Todo.js` file. We'll replace our temporary in-memory solution with the API we just defined.
+
+Start by importing our new file.
+
+```diff
+> src/Todo.js
+---
+ import TodoForm from './TodoForm';
+
++import BinarisAPI from './BinarisAPI';
+
+ class Todo extends Component {
+```
+
+We made an assumption while writing `BinarisAPI.js` that the root endpoint would be passed into the constructor. Of course it's possible to hardcode the URL here, but why not opt for a more elegant solution. `create-react-app` supports environment variables out of the box so let's utilize them.
+
+In our `Todo` constructor we'll check to see if the expected environment variable exists, if it doesn't we'll throw an error. If it does exist we can then use it to create our backend API.
+
+```diff
+> src/Todo.js
+---
+ constructor(props) {
+ super(props);
+ this.state = { todos: {} };
++
++ if (!process.env.REACT_APP_BINARIS_ROOT_ENDPOINT) {
++ throw new Error('Environment variable "REACT_APP_BINARIS_ROOT_ENDPOINT" is required!');
++ }
++ this.backend = new BinarisAPI(process.env.REACT_APP_BINARIS_ROOT_ENDPOINT);
+ }
+```
+
+The backend is now initialized in our `Todo` application. Now, all we're left to do is update our existing operations to utilize the remote state.
+
+Starting with `removeTodo`, the changes are incredibly minimal. It's safe to delete without looking at the response, considering an error in the request will make the next two lines unreachable.
+
+```diff
+> src/Todo.js
+---
+ removeTodo = async todoID => {
++ await this.backend.deleteItem(todoID);
+ const todos = { ...this.state.todos };
+ delete todos[todoID];
+ this.setState({ todos });
+ }
+```
+
+`createTodo` is a bit trickier but still pretty straightforward. The backend now has responisbility for unique ID generation, so we can remove it in the frontend. Additionally, we now use the direct response from the backend to populate the new todo item in our state.
+
+```diff
+> src/Todo.js
+---
+ createTodo = async todoText => {
+- const uniqueID = uuidv4();
++ const newItemData = await this.backend.createItem(todoText);
+ this.setState({
+ todos: {
+ ...this.state.todos,
+- [uniqueID]: todoText,
++ ...newItemData,
+ }
+ });
+ }
+```
+
+Don't forget to remove the now unused `uuid`.
+
+```diff
+> src/Todo.js
+---
+ import React, { Component } from 'react';
+ import Typography from '@material-ui/core/Typography';
+-import uuidv4 from 'uuid/v4';
+
+ import './index.css';
+```
+
+If we were to test things right now, they would be mostly functional. However, you would quickly notice that refreshes to the page fail to maintain the previous state. This is because we don't actually fetch the initial state when we initialize our frontend. Your initial thought might be to add this functionality into the `constructor`, but unfortunately, it won't work. There are two reasons the `constructor` can't be used to accomplish this,
+
+1. In ECMAScript the `constructor` is a reserved keyword and cannot be declared `async`. This means our remote backend calls would need to be handled elsewhere causing a mess.
+
+2. When using React, a constructor being called doesn't indicate that the Component itself has been added to the root `div`. Instead, React provides a dedicated method which is guaranteed to be called when your Component is fully intialized and mounted, `componentDidMount`.
+
+By making `componentDidMount` async we can use it to load in our initial backend state.
+
+```diff
+> src/Todo.js
+---
+ ...newItemData,
+ }
+ });
+ }
+
++async componentDidMount() {
++ const existingData = await this.backend.readAllItems();
++ this.setState({ todos: existingData || {} });
++}
+```
+
+Now our todo list will be populated with the initial backend state every time it's loaded.
+
+
+
+
+
+ Final state of Todo.js
+
+```JavaScript
+import React, { Component } from 'react';
+import Typography from '@material-ui/core/Typography';
+
+import './index.css';
+
+import TodoList from './TodoList';
+import TodoForm from './TodoForm';
+
+import BinarisAPI from './BinarisAPI';
+
+class Todo extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { todos: {} };
+ if (!process.env.REACT_APP_BINARIS_ROOT_ENDPOINT) {
+ throw new Error('Environment variable "REACT_APP_BINARIS_ROOT_ENDPOINT" is required!');
+ }
+ this.backend = new BinarisAPI(process.env.REACT_APP_BINARIS_ROOT_ENDPOINT);
+ }
+
+ createTodo = async todoText => {
+ const newItemData = await this.backend.createItem(todoText);
+ this.setState({
+ todos: {
+ ...this.state.todos,
+ ...newItemData,
+ },
+ });
+ }
+
+ removeTodo = async todoID => {
+ await this.backend.deleteItem(todoID);
+ const todos = { ...this.state.todos };
+ delete todos[todoID];
+ this.setState({ todos });
+ }
+
+ async componentDidMount() {
+ const existingData = await this.backend.readAllItems();
+ this.setState({ todos: existingData || {} });
+ }
+
+ render() {
+ return (
+
+
+ Todo
+
+
+
+
+ );
+ }
+}
+
+export default Todo;
+```
+
+
+
+The code is done but we have two little steps left before things will work.
+
+```bash
+$ npm install url-join
+```
+
+And lastly, export the root endpoint environment variable.
+
+```bash
+$ export REACT_APP_BINARIS_ROOT_ENDPOINT="https://run.binaris.com/v2/run/1234******/"
+```
+
+Now, just rebuild and redeploy and everything will work.
+
+```bash
+$ npm run build && npm run deploy
+```
diff --git a/tutorial_sections/develop_frontend.md b/tutorial_sections/develop_frontend.md
new file mode 100644
index 0000000..0ae49a4
--- /dev/null
+++ b/tutorial_sections/develop_frontend.md
@@ -0,0 +1,1106 @@
+# Develop a Frontend on Your Local Machine
+
+This section walks through building a todo list in React at a high level. If you're interested in learning React, we highly recommend the following [official React tutorial](https://reactjs.org/tutorial/tutorial.html)
+
+## Table of Contents
+1. [Setting Up the Project](#setting-up-project)
+1. [Creating Our Central Todo Component](#central-todo)
+1. [Creating and Manipulating State](#create-and-manipulate-state)
+1. [Displaying Todo Items](#displaying-todo-items)
+1. [Adding New Todos](#adding-new-todos)
+1. [Beast to Beauty](#beast-to-beauty)
+
+
+
+### Setting Up the Project
+
+React is a framework that allows complex frontend design to be expressed through native JavaScript code (or jsx files). In this tutorial we will go through the individual steps required to make your own React todo application. This goal could definitely be accomplished using vanilla React, but to save us some time and uneeded busy work we will use the `create-react-app` framework. `create-react-app` handles the traditionally tedious project creation and boilerplate generation steps, allowing us to immediately start writing our app relevant logic.
+
+
+```bash
+# create boilerplate project
+$ npx create-react-app frontend
+```
+
+Now that our files are generated let's test it
+```bash
+$ cd frontend
+$ npm run start
+```
+
+
+
+### Creating Our Central Todo Component
+
+Let's start off by removing some unnecessary files.
+
+```diff
+-src/index.css
+-src/App.js
+-src/App.test.js
+-src/logo.svg
+-src/App.css
+-src/serviceWorker.js
+```
+
+Next, create a file named `Todo.js` with the following contents in the `src` directory.
+
+```JavaScript
+import React, { Component } from 'react';
+
+class Todo extends Component {
+ render() {
+ return (
+
+
Hello
+
+ );
+ }
+}
+
+export default Todo;
+```
+
+Todo Component explained
+
+The first line brings the React library into our current scope, nothing special.
+
+```JavaScript
+import React, { Component } from 'react';
+```
+
+Here we define a new JS class named "Todo" which extends something called `Component`. A Component is the building block of any React application. Components are almost completely flexible but they generally accept input and return some rendered portion of the user interface. Our `Todo` Component will hold the entire Todo application.
+
+```JavaScript
+class Todo extends Component {
+```
+
+The [official React documentation](https://reactjs.org/docs/components-and-props.html) is very helpful here
+
+Inside of the `Todo` class we define a method named `render`.
+
+`render` is a method inherited from Component and is expected to return the rendered contents representing your Component. Our current render code is very simple and just returns a header tag "Hello"
+
+```Javascript
+render() {
+ return (
+
+
Hello
+
+ );
+}
+```
+
+Lastly, we export our `Todo` class so it's available to use in external files (such as `src/index.js`)
+
+```JavaScript
+export default Todo;
+```
+
+
+
+
+Because we deleted `src/App.js` we will need to update our `src/index.js` to use the new `src/Todo.js` file
+
+```diff
+> src/index.js
+---
+-import './index.css';
+-import App from './App';
+-import * as serviceWorker from './serviceWorker';
++import Todo from './Todo';
+...
+-ReactDOM.render(, document.getElementById('root'));
++ReactDOM.render(, document.getElementById('root'));
+...
+-// If you want your app to work offline and load faster, you can change
+-// unregister() to register() below. Note this comes with some pitfalls.
+-// Learn more about service workers: http://bit.ly/CRA-PWA
+-serviceWorker.unregister();
+```
+
+We are also going to need a few dependencies and might as well install those now. Do so by running
+
+`npm install @material-ui/core @material-ui/icons url-join uuid` inside the `frontend` directory.
+
+
+Before we move on to the next section, let's test and make sure we didn't break everything by running...
+
+`npm run start`
+
+You should see a minimal (but working) webpage in your browser
+
+Now that we're on the same page, let's start adding to our application. We'll start off slow and simply improve the text displayed when you visit the todo app in a browser. Currently native `` tags are used but we can do better. Instead of `` tags let's rely on a `Typography` element from the `material-ui` library we installed in the previous section.
+
+```diff
+> src/Todo.js
+---
+-Hello
++
++ Todo
++
+```
+
+To use `Typography` the correct dependency should be imported
+
+```diff
+> src/Todo.js
+---
+ import React, { Component } from 'react';
++import Typography from '@material-ui/core/Typography';
+```
+
+Test in your browser to see the slightly updated "Todo" text.
+
+
+
+### Creating and Manipulating State
+
+[Just Show Me the Code](#create-and-manipulate-state-code)
+
+We'll want to create external files and Components for displaying the todo items, but it's usually a good idea to have a centralized representation of state. We can achieve this by adding a constructor to the `Todo` class and define the format of our initial state. To keep things simple we'll store our todo items using a basic key value mapping.
+
+```diff
+> src/Todo.js
+---
+ class Todo extends Component {
++ constructor(props) {
++ super(props);
++ this.state = { todos: {} };
++ }
+```
+It's important to call `super(props)` because we want the parent Component initialization to still take place.
+
+Now that we know our state format in the `Todo` class, it's probably best to write some "accessor" functions so that we avoid directly modifying values. To start, let's define a function that creates a new todo.
+
+```diff
+> src/Todo.js
+---
+ import React, { Component } from 'react';
+ import Typography from '@material-ui/core/Typography';
++import uuidv4 from 'uuid/v4';
+...
+ this.state = { todos: {} };
+ }
++
++createTodo = async todoText => {
++ const uniqueID = uuidv4();
++ this.setState({
++ todos: {
++ ...this.state.todos,
++ [uniqueID]: todoText,
++ },
++ });
++}
+```
+
+createTodo explained
+
+Our `createTodo` function needs to take in the text representation of the new todo. We use the arrow operator because it allows the function body to use `this` to refer to our `Todo` class scope instead of the caller scope. If you're not sure what I mean [here](https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4) is a nice article that explains.
+
+```diff
+> src/Todo.js
+---
++createTodo = async todoText => {
+```
+
+The `uuid` module is used to generate a unique key for our todo entry. `this.setState` is used because directly modifying `this.state` is not possible in a React Component.
+
+```diff
+> src/Todo.js
+---
++const uniqueID = uuidv4();
++this.setState({
++ todos: {
++ ...this.state.todos,
++ [uniqueID]: todoText,
++ },
++});
+```
+
+Our final step is to add the import for the `uuid` dependency to ensure it's accessible.
+
+```diff
+> src/Todo.js
+---
+ import React, { Component } from 'react';
+ import Typography from '@material-ui/core/Typography';
++import uuidv4 from 'uuid/v4';
+```
+
+
+
+The ability to create a todo has been added but now we probably also want the ability to remove a todo. Because of the decisions we made in `createTodo` we can assume that `removeTodo` will receive that same uuid based `todoID` that was generated and stored at creation.
+
+```diff
+> src/Todo.js
+---
++removeTodo = async todoID => {
++ const todos = { ...this.state.todos };
++ delete todos[todoID];
++ this.setState({ todos });
++}
+```
+
+
+removeTodo explained
+
+
+Just as with create, our removeTodo function takes a single argument. Instead of todo data it instead accepts an ID which represents some previously created todo item. Because we use `this` inside of the function, the arrow operator is crucial.
+
+```diff
+> src/Todo.js
+---
++removeTodo = async todoID => {
+```
+
+The body of `removeTodo` is straightforward enough. We copy `this.state` and delete the ID of the provided todo. Then, using the proper `this.setState` function we update the state to reflect the removal.
+
+```diff
+> src/Todo.js
+---
+ removeTodo = async todoID => {
++ const todos = { ...this.state.todos };
++ delete todos[todoID];
++ this.setState({ todos });
++}
+```
+
+
+
+
+ Current state of Todo.js
+
+```JavaScript
+import React, { Component } from 'react';
+import Typography from '@material-ui/core/Typography';
+import uuidv4 from 'uuid/v4';
+
+class Todo extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { todos: {} };
+ }
+
+ createTodo = async todoText => {
+ const uniqueID = uuidv4();
+ this.setState({
+ todos: {
+ ...this.state.todos,
+ [uniqueID]: todoText,
+ },
+ });
+ }
+
+ removeTodo = async todoID => {
+ const todos = { ...this.state.todos };
+ delete todos[todoID];
+ this.setState({ todos });
+ }
+
+ render() {
+ return (
+
+
+ Todo
+
+
+ );
+ }
+}
+
+export default Todo;
+```
+
+
+
+
+### Displaying Todo Items
+
+[Just Show Me the Code](#displaying-todo-items-code)
+
+Now that we have modifiable state, we can start building the actual Components which will manipulate that state. Considering that this is a todo application we should probably add a way to display todos.
+
+Start by creating a new file in `src` named `TodoList.js`. `TodoList.js` will be our Component responsible for displaying and interacting with todos. Just as with `Todo.js` we'll want to define a class inside `TodoList.js` called... you guessed it `TodoList`. Since we know that every Component must have a valid `render` method defined, let's also go ahead and create an empty stub too.
+
+```diff
+> src/TodoList.js
+---
++class TodoList extends Component {
++ render() {}
++}
++
++export default TodoList;
+```
+
+The next step in building out our `TodoList` class is to bring in the accessors and data that we created in the previous section. We haven't actually passed this data to `TodoList` on the caller side so we'll have to make a mental note and get back to it when we finish the work on our list.
+
+```diff
+> src/TodoList.js
+---
+ render() {
++ const { removeTodo, todos } = this.props;
+ }
+```
+
+
+About Components
+
+A defining characteristic of React Components is their ability to receive input. All Components share a constructor signature that takes a minimum of 1 argument, `props`. Props amongst other things holds any potential input that was passed in from the caller. Assuming the base functionality is not overwritten, this `props` data will then be available throughout the lifetime of the Component.
+
+
+
+
+We've defined our class, it's render method and taken in the state and accessors, so now it's time to actually do something with it. Once again we can start off easy and just add our outermost tags in the return statement.
+
+```diff
+> src/TodoList.js
+---
+ const { removeTodo, todos } = this.props;
++ return(
++
++
++
++ );
+ }
+```
+
+The `List` component from Material-UI should simplify our process of creating a dynamic todo display. But because it's from an external library we'll need to import it at the top of our `TodoList.js` file. While we're at it, let's get ahead of ourselves and import the other items we'll need to complete our list.
+
+```diff
+> src/TodoList.js
+---
++import React, { Component } from 'react';
++
++import CheckIcon from '@material-ui/icons/Check';
++import IconButton from '@material-ui/core/IconButton';
++import List from '@material-ui/core/List';
++import ListItem from '@material-ui/core/ListItem';
++import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
++import ListItemText from '@material-ui/core/ListItemText';
+
+ class TodoList extends Component {
+```
+
+It may seem like a lot but it's really not, so don't get overwhelmed.
+
+Moving back to our `render` method remember that we need to fill in the `
` with all of the todos our user has added. Using the `todos` variable that we deconstructed from the input `props` we can programatically create a list item for each todo. Native JavaScript maps make this task a breeze.
+
+```diff
+> src/TodoList.js
+---
+ return(
+
++ {
++ Object.keys(todos).map(todoID => (
++
++
++
++ ))
++ }
+
+ );
+```
+
+Map over all of the `todos` using the dict key (which will have been generated using `uuid` in our createTodo method) as a unique identifier for the `ListItem`.
+
+At this point our list has a bunch of items, but they don't have any data which allows them to be displayed. Knowing that each item should show the relevant todo text, let's add a sub-component to each `ListItem` that simply displays the todo text.
+
+```diff
+> src/TodoList.js
+---
+ Object.keys(todos).map(todoID => (
+
++
+
+ ))
+```
+
+While displaying the todo is a nice step, we probably want some way to remove the todo when the item has been completed. To accomplish this we can add a secondary action to our outer `ListItem` and connect that to the functionality we defined in `removeTodo`.
+
+```diff
+> src/TodoList.js
+---
+ Object.keys(todos).map(todoID => (
+
+
++
++ removeTodo(todoID)}
++ >
++
++
++
+
+ ))
+```
+
+Closer look at remove functionality
+
+Because we've already added `ListItemText` as our primary component in the `ListItem`, we use `ListItemSecondaryAction` so we can define the additional remove functionality.
+
+```diff
+> src/TodoList.js
+---
+
+
++
++
+
+```
+
+There needs to be some trigger on our list which can fire the `remove` event. The simplest and most obvious way to accomplish this is with a Button. Specfically we use `IconButton` because it allows us to use an informative Icon for our button, this hopefully makes the functionality self explanatory to the user.
+
+```diff
+> src/TodoList.js
+---
+ Object.keys(todos).map(todoID => (
+
+
+
++ removeTodo(todoID)}
++ >
++
+
+
+ ))
+```
+
+The `aria-label` is simply an internal label used to identify the buttons purpose. More important is `onClick`, this defines what code will be called when the button is pressed. As you can see we hook up `removeTodo` using the `todoID` of the `` who owns the button.
+
+Lastly, we want an icon that communicates the functionality to the user. Usually you remove things from a todo list when you've completed what you need to do. To me, a `Check` communicates this functionality so I'll use the `CheckIcon` I imported earlier. If you feel like my Icon foo is subpar, [here's a link](https://material-ui.com/style/icons/) to all available icons so you can customize with your own choice.
+
+
+```diff
+> src/TodoList.js
+---
+ Object.keys(todos).map(todoID => (
+
+
+
+ removeTodo(todoID)}
+ >
++
+
+
+
+ ))
+```
+
+
+
+
+And that's it, `TodoList` is complete.
+
+
+The last step is to integrate our new `TodoList` in our centralized `Todo` Component. Keeping in mind the mental note made in the previous section, we make sure to pass the correct input fields so our `TodoList` can operate on the stateful data.
+
+```diff
+> src/Todo.js
+---
+
+
+ Todo
+
++
+
+ );
+```
+
+And of course, don't forget to import
+
+```diff
+> src/Todo.js
+---
+ import React, { Component } from 'react';
+ import Typography from '@material-ui/core/Typography';
+
++import TodoList from './TodoList';
+```
+
+
+
+ Final state TodoList.js
+
+```JavaScript
+import React, { Component } from 'react';
+
+import CheckIcon from '@material-ui/icons/Check';
+import IconButton from '@material-ui/core/IconButton';
+import List from '@material-ui/core/List';
+import ListItem from '@material-ui/core/ListItem';
+import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
+import ListItemText from '@material-ui/core/ListItemText';
+
+class TodoList extends Component {
+ render() {
+ const { removeTodo, todos } = this.props;
+ return(
+
+ {
+ Object.keys(todos).map(todoID => (
+
+
+
+ removeTodo(todoID)}
+ >
+
+
+
+
+ ))
+ }
+
+ );
+ }
+}
+
+export default TodoList;
+```
+
+
+
+
+ Current state of Todo.js
+
+```JavaScript
+import React, { Component } from 'react';
+import Typography from '@material-ui/core/Typography';
+import uuidv4 from 'uuid/v4';
+
+class Todo extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { todos: {} };
+ }
+
+ createTodo = async todoText => {
+ const uniqueID = uuidv4();
+ this.setState({
+ todos: {
+ ...this.state.todos,
+ [uniqueID]: todoText,
+ },
+ });
+ }
+
+ removeTodo = async todoID => {
+ const todos = { ...this.state.todos };
+ delete todos[todoID];
+ this.setState({ todos });
+ }
+
+ render() {
+ return (
+
+
+ Todo
+
+
+
+ );
+ }
+}
+
+export default Todo;
+```
+
+
+
+Sanity Check
+Although not required, it's usually a good idea to make sure that things work by checking them incrementally. Although there is no external way to add todo items (and therefore validate that our `TodoList` is working), we can add a few temporary lines to fake some user input.
+
+All that we need to do is modify the initial `this.state` we set in the constructor of `Todo.js` to have some pre-made entries. Once that's done, it should be as easy as running the React project locally.
+
+```diff
+> src/Todo.js
+---
+ constructor(props) {
+ super(props);
+- this.state = { todos: {} };
++ this.state = {
++ todos: {
++ 231231: 'Go shopping',
++ 298393: 'Eat dinner',
++ BOGUS: 'anything else that was forgotten',
++ },
++ };
+ }
+```
+
+Don't forget to revert the change once you feel comfortable with the results!
+
+
+
+
+
+### Adding New Todos
+
+[Just Show Me the Code](#adding-new-todos-code)
+
+We now have centralized state and a way to display that state, but one thing is still missing, the ability to add new todo items. To add a new todo, users will need some type of input field along with a potential button(s) to trigger the `addTodo` event. Let's start as we did for `TodoList` and create a new file in `src` for our input *form* named `TodoForm.js`.
+
+```diff
+> src/TodoForm.js
+---
++class TodoForm extends Component {
++ render() {}
++}
++
++export default TodoForm;
+```
+
+It's clear that the `render` method will need to be filled in before things can start working, but before doing that let's make sure we know what our goal and need(s) are. As we previously discussed, the form should provide the ability to externally add a todo which probably means this Component needs to track the intermediate representation. We'll accomplish this by adding a default todo item and then setting the initial state to the default in the constructor
+
+```diff
+> src/TodoForm.js
+---
++const EMPTY_TODO = {
++ todoText: '',
++};
++
+ class TodoForm extends Component {
++ constructor(props) {
++ super(props);
++ this.state = Object.assign({}, EMPTY_TODO);
++ }
+ render() {}
+```
+
+The state of our `TodoForm` component will now be used to track the current todo text. Before we move onto our `render` method, let's once again import all the dependencies needed.
+
+```diff
+> src/TodoForm.js
+---
++import React, {Component} from 'react';
+
++import AddIcon from '@material-ui/icons/Add';
++import IconButton from '@material-ui/core/IconButton';
++import TextField from '@material-ui/core/TextField';
+
+ const EMPTY_TODO = {
+ todoText: '',
+ };
+
+ class TodoForm extends Component {
+```
+
+The first addition to our `render` method is intended to make our lives a bit easier. At the start of every call to render, we'll extract the text field from the state.
+
+```diff
+> src/TodoForm.js
+---
+ render() {
++ const { todoText } = this.state;
+ }
+```
+
+Now we can get down to business. Let's add a `form` to `render` which will fire events related to todo additions. Specifically we want to handle the event where a user hits the `enter` key while typing in the form.
+
+```diff
+> src/TodoForm.js
+---
+ const { todoText } = this.state;
++ return(
++
++ );
+ }
+```
+
+As you may have noticed, in the `onSubmit` event handler we refer to a method named `addTodo` which hasn't yet been defined. Now that we understand the structure we can accurately define it. By default the input would be ignored, but by consuming the input and calling out to an external function we can take responsibility for this specific event.
+
+```diff
+> src/TodoForm.js
+---
+ constructor(props) {
+ super(props);
+ this.state = Object.assign({}, EMPTY_TODO);
+ }
+
++addTodo = (event) => {
++ event.preventDefault();
++ if (this.state.todoText === '') return
++ this.props.createTodo(this.state.todoText);
++ this.setState(EMPTY_TODO);
++}
+```
+
+The tricky line `this.props.createTodo(this.state.todoText);` makes an assumption that our `TodoForm` props already has the centralized `createTodo` method that we made in `Todo.js`. Once again we should keep a mental note and remember to pass it in when we move back to `Todo.js`.
+
+Although we have a `form`, it doesn't yet allow users to actually type anything in. Let's fix this by adding a nested `TextField` Component into our form body.
+
+```diff
+> src/TodoForm.js
+---
+ }}
+ >
++
+
+```
+
+`TextField` from `material-ui` takes in a `eventListener` for the `onChange` event. This will be called every time the text is updated, added or removed. Just as with `addTodo` we now need to define and implement the method `updateTodoText` to handle the onChange event.
+
+```diff
+> src/TodoForm.js
+---
+ addTodo = (event) => {
+ event.preventDefault();
+ if (this.state.todoText === '') return
+ this.props.createTodo(this.state.todoText);
+ this.setState(EMPTY_TODO);
+ }
++
++updateTodoText = event => {
++ const { value } = event.target;
++ this.setState({ todoText: value });
++}
+```
+
+Technically, we have everything needed for a working input form. But right now, users are limited to adding todo items with the `enter` key. As a final step, let's add a "submit" button which has identical functionality to the `enter` key we already set up. Luckily, we can reuse the previously defined `addTodo` method for our new button.
+
+```diff
+> src/TodoForm.js
+---
+
++
++
++
+
+```
+
+And with that we have our final change to `TodoForm`. In fact, not only are we done with `TodoForm`, we're also a single change away from having a fully functional app. The last thing to address is the mental note we made in the previous section. Let's go back to `Todo.js` and add the recently created `TodoForm` (while not forgetting to pass the `createTodo` as input).
+
+```diff
+> src/Todo.js
+---
+
+ Todo
+
++
+ src/Todo.js
+---
+ import React, { Component } from 'react';
+ import Typography from '@material-ui/core/Typography';
+
+ import TodoList from './TodoList';
++import TodoForm from './TodoForm';
+```
+
+
+
+ Current state of TodoForm.js
+
+```JavaScript
+import React, { Component } from 'react';
+
+import AddIcon from '@material-ui/icons/Add';
+import IconButton from '@material-ui/core/IconButton';
+import TextField from '@material-ui/core/TextField';
+
+const EMPTY_TODO = {
+ todoText: '',
+};
+
+class TodoForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state = Object.assign({}, EMPTY_TODO);
+ }
+
+ updateTodoText = event => {
+ const { value } = event.target;
+ this.setState({ todoText: value });
+ }
+
+ addTodo = (event) => {
+ event.preventDefault();
+ if (this.state.todoText === '') return
+ this.props.createTodo(this.state.todoText);
+ this.setState(EMPTY_TODO);
+ }
+
+ render() {
+ const { todoText } = this.state;
+ return(
+
+ );
+ }
+}
+
+export default TodoForm;
+```
+
+
+
+
+ Current state of Todo.js
+
+
+```JavaScript
+import React, { Component } from 'react';
+import Typography from '@material-ui/core/Typography';
+import uuidv4 from 'uuid/v4';
+
+import TodoList from './TodoList';
+import TodoForm from './TodoForm';
+
+class Todo extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { todos: {} };
+ }
+
+ createTodo = async todoText => {
+ const uniqueID = uuidv4();
+ this.setState({
+ todos: {
+ ...this.state.todos,
+ [uniqueID]: todoText,
+ },
+ });
+ }
+
+ removeTodo = async todoID => {
+ const todos = { ...this.state.todos };
+ delete todos[todoID];
+ this.setState({ todos });
+ }
+
+ render() {
+ return (
+
+
+ Todo
+
+
+
+
+ );
+ }
+}
+
+export default Todo;
+```
+
+
+
+
+You can either check the result by rebuilding and redeploying your function, or by simply running `npm run start` to see a local representation in your browser.
+
+
+
+### Beast to Beauty
+
+[Just Show Me the Code](#beast-to-beauty-code)
+
+Our todo list is fully functional, but with two small changes we can make it visually appealing as well. The first improvement is adding some simple css that will center our content and give us control over the font. Let's add a new file in `src` named `index.css`, all we'll do is center align the text and choose a wacky font.
+
+```diff
+> src/index.css
+---
++.Todo {
++ font-family: fantasy;
++ text-align: center;
++}
+```
+
+All that's left is to import it in `Todo.js`
+
+```diff
+> src/Todo.js
+---
+ import Typography from '@material-ui/core/Typography';
+ import uuidv4 from 'uuid/v4';
+
++import './index.css';
+
+ import TodoList from './TodoList';
+```
+
+This centered most of our content but it actually caused our input form to become offset from the title. To fix this, we'll use an inline style on our form, there are definitely more elegant solutions but it gets the job done for now.
+
+
+```diff
+> src/TodoForm.js
+---
+