Skip to content
Fernando Trigoso edited this page Jun 23, 2017 · 23 revisions

API Server

Solution branch: sln/00-api-server-v2

Initial Setup

  • Make sure you are running the LTS version of Node.js (as of May 2017 the LTS version was v6.10.3)

  • Create package.json:
    $ npm init

  • Add /node_modules and /dist to .gitignore

Install babel

Babel transpiles Javascript ES5 to ES6. Node v6 has 99% support for ES6, except for ES6 modules. We will use Babel to only transpile our ES6 modules (i.e. import and export statements).
http://babeljs.io/docs/plugins/transform-es2015-modules-commonjs/

  • Install babel-node:
    $ npm install babel-cli --save

  • Install babel modules plugin
    $ npm install babel-plugin-transform-es2015-modules-commonjs --save

  • Create .babelrc file and configure babel to only use the modules plugin

{
  "plugins": ["transform-es2015-modules-commonjs"]
}

Let's test the babel setup

  • Create src/test-babel.js with code:
import path from 'path';

const directories = ['/foo', 'bar', 'baz/abc', 'qwerty', '..'];

const result = path.join(...directories);

console.log(result);
  • Setup babel in package.json:
"scripts": {
"build": "rm -Rf dist && babel src -d dist"
}
  • Build it:
    $ npm run build

  • Test it:

        $ node dist/test-babel.js 	// should work
        $ node src/test-babel.js 	// should fail

Create simple test server

  • Create simple test server in src/test-server.js
import http from 'http';

const port = process.env.PORT || 4000;

const server = http.createServer((req, res) => {
	res.statusCode = 200;
	res.setHeader('Content-Type', 'text/plain');
	res.end('Hello React Class\n');
});

server.listen(port, () => {
	console.log(`Server running at port ${port}.`);
});
  • Build it:
    $ npm run build

  • Run the test server using node:
    $ node dist/test-server.js

  • Test it using your browser.

Setup babel-node

babel-node will compile ES6 code before running it with node. It will help us avoid having to run build all the time. babel-node comes with babel-cli install.

  • Add "babel-node" to package.json:
"scripts": {
"babel-node": "babel-node"
}
  • Test it:
    $ npm run babel-node src/test-server.js

Setup nodemon

It monitors file changes and automatically reloads your server.

  • Install it locally:
    $ npm install nodemon --save-dev

  • In package.json:

"scripts": {
"nodemon": "nodemon --exec npm run babel-node"
}
  • Test it:
    $ npm run nodemon src/test-server.js

  • Make a change to test-server.js and see how it restarts, then hit server with your browser.

Setup ESLint

A code quality tool, checks Javascript syntax.
http://eslint.org/docs/rules/

  • Install it and configure it:
    $ npm install eslint --save-dev
    $ ./node_modules/.bin/eslint --init

    ? How would you like to configure ESLint? Answer questions about your style
    ? Are you using ECMAScript 6 features? Yes
    ? Are you using ES6 modules? Yes
    ? Where will your code run? Browser, Node
    ? Do you use CommonJS? No
    ? Do you use JSX? Yes
    ? Do you use React Yes
    ? What style of indentation do you use? Tabs
    ? What quotes do you use for strings? Single
    ? What line endings do you use? Unix
    ? Do you require semicolons? Yes
    ? What format do you want your config file to be in? JSON
  • Test it:
    $ ./node_modules/.bin/eslint src/*

  • git commit changes

Deploy to Heroku

https://devcenter.heroku.com/articles/getting-started-with-nodejs# introduction

  • Install Heroku CLI.

  • Login and create heroku app:

    $ heroku login
    $ heroku create
  • In package.json:
    "scripts": {
        "postinstall": "npm run build"
    }
  • In Procfile:
    web: node dist/test-server.js

  • In package.json:

    "engines": {
"node": "6.10.3",
"npm": "3.10.10"
    }
  • create release branch

  • git commit and merge to release branch

  • Deploy to heroku from release branch.
    $ git push heroku release:master

  • Launch app in heroku
    $ heroku open

  • If it fails, you can check logs:
    $ heroku logs --tail

Setup express.js

Popular web server for node.js

  • $ npm install express --save

  • Create api-server.js with sample code below:

import express from 'express';

const app = express();
const port = process.env.PORT || 4000;

app.get('/', (req, res) => {
	res.send('Hello React Class, from Express.js!');
});


app.listen(port, () => {
	console.log(`Express app listening on port ${port}`);
});
  • Test it with nodemon and your browser

Setup Redis

  • Install redis
    http://redis.io/download

  • Make sure your local redis server is running

  • Install the redis client for node
    $ npm install redis --save

  • Create redis-client.js that exposes a setupRedis function and the redisClient

  • Call setupRedis from api-server.js

  • Add /redis-test route that increments a 'inc-test' key in redis and returns the new incremented value

  • In heroku: add redis as an add-on
    $ heroku addons:create rediscloud:30

  • Deploy to heroku and test redis route

Web Server

Solution branch: sln/01-web-server-v2

Organize directories

  • Organize js files into spa, core and api directories.

  • Update run scripts and rename “build” to “build-api”
    "build-api": "rm -Rf dist/api && babel src/api -d dist/api",
    "postinstall": "npm run build-api"

  • Build api js files using
    $ npm run build-api

  • Update Procfile

  • Release to Heroku, test that the api-server works as expected

React and React-DOM

  • Install react and react-dom. Since we are going to build spa locally, install them into devDependencies:
    $ npm install react --save-dev
    $ npm install react-dom --save-dev

Setup babel for the spa

Browsers don’t support ES6 yet. Therefore, we need to transpile all of our spa code.

  • Setup babel presets:
    es2015 preset is for syntax transformation.
    react preset is for react and jsx.
    polyfill is to allow es2016+ like Set, Maps, Promises
    $ npm install babel-preset-es2015 --save-dev
    $ npm install babel-preset-react --save-dev
    $ npm install babel-polyfill --save-dev

Entry point of our spa

  • Create src/spa/app.js, this will be the entry point of our spa.

  • Make sure to import babel-polyfill at the top of file.
    import 'babel-polyfill';

  • app.js will import react and react-dom and will render a simple message for now.

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<h1>Hello React Class</h1>, document.getElementById('root'));

Setup webpack

  • Install webpack, webpack-dev-server and babel-loader as devDependencies
    $ npm install webpack webpack-dev-server babel-loader --save-dev

  • Add webpack.config.js file
    (see sln branch)

  • Install gulp to help us run more complex tasks (like webpack dev server and build)
    $ npm install gulp --save-dev

  • Add gulpfile.js
    (see sln branch)

  • Add www/index.html
    (see sln branch)

  • Add npm scripts:
    "build-spa": "rm -Rf dist/spa && gulp build-spa",
    "spa-server": "gulp spa-server",

  • Test build-spa, it should output app.bundle.js and app.bundle.js.map to dist/spa
    $ npm run build-spa

  • Test spa-server, it should run the webpack development server which reloads on file changes
    $ npm run spa-server

Integrate spa and api using a json route

  • In the api, add /json-test route. It should also increment using Redis and it should return an object using res.json({}).

  • The spa will make ajax calls using jquery ajax library
    $ npm install jquery --save-dev

  • Create a react component that will make an ajax call to the /test-json route and will render result.

  • Test json-test route using react component

Setup static site

We need a simple site that will host our index.html and static assets (app.bundle.js, images, etc.). We can use github pages for that.

  • Fork my wa-clone-site repo:
    https://github.com/fertrig/wa-clone-site

  • On your fork: click on the Settings tab and scroll down to the GitHub Pages section. Then select the master branch source and click on the Save button.
    https://pages.github.com/

  • Delete the spa/ directory contents

  • Update index.html with your apiUrl

  • Build the spa in the main repo, then copy the app.bundle.js and app.bundle.js.map over to the site repo, make sure to version it

  • Commit and push to the site repo.

  • Deploy your api to heroku

  • Go to your github site to verify spa is using the api hosted in heroku:
    https://[username].github.io/wa-clone-site/

  • Homework: write a gulp task that will automate this process

Setup Profile

Solution branch: sln/02-setup-profile-v2

API route to create user

Create an api route that will create a user in Redis.

  • Route will accept a json object
    $ npm install body-parser --save

  • User requirements

    • A user has a handle and name.
    • Validation rules:
      • handle must be unique,
      • handle cannot be more than 16 characters

Universal Javascript

Some of our code will run on the server and the client. We need a strategy to share and build this code. A simple strategy is to identify folders that will have files that will run on both client and server.

  • Validation rules will run on both client and server, place them in the core directory

  • Adjust the build-api script to also transpile the core directory
    "build-api": "rm -Rf dist/api && rm -Rf dist/core && babel src/api -d dist/api && babel src/core -d dist/core"

Setup tests

  • Create a sample mocha test under core/__tests__/sample.tests.js

  • Install and setup mocha and chai
    $ npm install mocha --save-dev
    $ npm install chai --save-dev

  • Create test-root.js file which will help us with test globals
    global.assert = require("chai").assert;

  • Setup an npm script for mocha that will run test files inside any __tests__ folder
    "test": "mocha ./test-root.js ./src/**/__tests__/*.tests.js --compilers js:babel-core/register"

  • $ npm test

  • Create unit tests for user validation

Create User

JWT Token

Create a token that will encode the user’s handle. We’ll use it as a quick way to authenticate requests. The api will sign the user handle using a key, that way only the api can decode it. Note that the token doesn't expire, which is not ideal. For a proper JWT implementation see:
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/ https://jwt.io/introduction/

  • If the validation succeeds, return the JWT token to the client, which will encode the user's handle.
    $ npm install jsonwebtoken --save

Setup Sass

We would like to require sass files from within our react components. To do so, we need to setup webpack and sass. There is a loader that will bundle our sass files but it needs other dependencies to work.

  • Install sass-loader and its peer dependencies
    $ npm install sass-loader node-sass style-loader css-loader --save

  • Configure the loader in webpack.config.js

loaders: [
    {
        test: /src(\/|\\).*\.scss$/,
        loaders: ["style", "css", "sass"]
    }
]

View to submit profile

  • Create React component that will submit profile. Feel free to use the UX provided. The spa will use the route to create users when submitting a profile.

  • Manage the request state. A request has several states: default, in-progress, success, hasError. Make sure you render the correct view for each request state.

  • If the request succeeds, store the jwt token in localStorage so that it can be used for subsequent requests and sessions. Be aware that some browsers only give you 5 megabytes of localStorage space.

  • Ajax request should pass Authorization header and cache control headers

  • Homework: use the user validator on the client-side. Validate that the handle and name are provided. Show messages to the user based on the validation errors.

Add Contact

Solution branch: sln/03-add-contact-v2

Stores and folder structure

  • Leverage a base store to reduce flux boilerplate code

  • Prefer to organize your folders by feature

Chats view zero state and Add Contact view

  • If the profile is already created, render the Chats view

  • If no contacts, render the zero state of the Chats view

Modals

  • Create a re-usable Modal component

  • Display the Add Contact modal.

  • User should be able to close the modal by clicking outside of it (on the modal overlay)

Reusable components

  • Refactor request state and submit button into a reusable component. This new component will manage the submit button state based on the request state. Can be re-used from add-profile, add-contact, edit-profile, etc. (Solution: see request-submit-button.react.js)

  • Create a component that will render a message for every request state (Solution: see request-message.react.js)

Make it real-time

Solution branch: sln/04-real-time

Setup socket.io

  • Initial setup.

  • Create namespace for each client.

  • Clients and server will communicate via facts. Facts will have types. Setup code to support facts.

Scrolling to bottom

  • When new messages come in we need to scroll down to the bottom so users can read the new messages.

Setup images

  • Configure webpack to process image assets. Use them from within the application.