JavaScript is awesome, just let me show you
Jean-Marcel Belmont
Software Engineer at Interactive Intelligence
React.js
React Router
Redux (Dispatch actions to Redux Store)
Sass
Node.js
Express.js
RethinkDB
Server starts at bin/www by setting port and grabbing self signed certificate before launching https server.
/**
* Get port from environment and store in Express.
*/
const port = normalizePort(process.env.PORT);
const IP_ADDRESS = process.env.IP_ADDRESS;
const options = {
key: fs.readFileSync(path.join(__dirname, '../ca/key.pem')),
cert: fs.readFileSync(path.join(__dirname, '../ca/cert.pem'))
};
app.set('port', port);
const server = https.createServer(options, app);
app.js is where logic begins for the Server.
Setting view rendering engine
Set middleware layer to parse json
Set routes that connect with express.js
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(favicon(path.join(__dirname, 'static/images', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'static')));
app.use('/', routes);
// Load payments api and admin interface api.
app.use('/api/v1/payments', payments);
app.use('/api/v1/admin', admin);
Easily handle CRUD (Create, Read, Update, Delete) operations with express.js
router.post('/receivePayment', (req, res, next) => {
const {stripeToken} = req.body;
return stripe.charges.create({
amount: 1000, // Amount in cents
currency: "usd",
source: stripeToken,
description: "Example charge"
})
.then(charge => res.send(charge))
.catch(err => winston.log('error', 'Error Creating Charge for stripe', {err}));
});
module.exports = router;
"use strict";
const rethinkdb = require('rethinkdb');
const winston = require('winston');
const recipes = require('./recipes')["recipes"];
const DB = { DATABASE_NAME: process.env.DATABASE_NAME || 'paradiso',
...
};
function dbActions() {
return connectToRethinkDBServer()
.then((connection) => {
DB.connection = connection;
return doesParadisoExist()
.then(exists => exists);
})
...
}
exports.dbActions = dbActions;
Here is a simple module that loads environment variables held in a hidden file.
module.exports = {
loadEnvironmentVars: require('dotenv').config()
}
Webpack is a module bundler that is configurable and has a large plugin ecosystem.
Babel is a JavaScript Transpiler that has many plugins for newer javascript features.
const webpack = require('webpack');
module.exports = {
entry: {
App: './static/js/components/App.jsx'
},
output: {
path: __dirname + '/static/build',
filename: '[name].js'
},
devtool: 'source-map',
...
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
...
Use gulp tasks and intergrate them with npm scripts.
// gulpfile here
gulp.task('start', () => {
nodemon({
script: './bin/www',
ignore: ['static/*'],
env: { 'PORT': '3000' }
});
});
gulp.task('build', (cb) => {
runSequence('copy:react:files', 'uglify:js', 'build:js', 'build:sass', 'build:vendor:sass', cb);
});
"scripts": {
"clean:build": "rimraf ./static/build",
"lint:watch": "npm run lint -- --watch",
"lint": "esw static/js",
"prestart:dev": "npm run clean:build",
"start:dev": "gulp dev",
"build": "npm run clean:build && gulp build",
"test": "jest tests"
}
babelrc is where you load up optional presets for babel compilation.
// .babelrc
{
"presets": ["es2015", "react", "stage-0"]
}
React.js is a powerful library built for user interfaces
Redux is a predictable state container for JavaScript apps.
React Router is a complete routing library for React.
FrontEnd Architecture for this app begins inside static/js
The folder structure is using a pattern in redux where you store reducers, actions, and the store in their own scope.
There is a some work to figuring how to connect react router, redux and react together.
import React, { Component } from 'react';
...
import Main from './Main.jsx';
import BakeryHome from './BakeryHome.jsx';
import Menu from './Menu.jsx';
...
import store, { history } from '../store/store';
In Redux you dispatch actions to the store.
Use reducers to initiate state change.
Component dispatches action to the Redux Store
_addToCart(evt) {
const item = evt.currentTarget.dataset["recipeName"];
const hasCartBeenAdded = cart.some(cartItem => {
return cartItem === item;
});
if (!hasCartBeenAdded) {
cart.push(item);
cartLength++;
store.dispatch(addToCart({
item,
cartLength
}));
}
}
Action is handled here:
export function addToCart({item, cartLength}) {
return {
type: 'ADD_TO_CART',
item,
cartLength
};
}
Reducer updates state with Redux Store
function checkoutCart(state = [], action) {
const {
type,
item,
cartLength
} = action;
switch (type) {
case 'ADD_TO_CART':
return [
...state,
{
cartLength,
cartItems: item
}
];
default:
return state;
}
}
export default checkoutCart;
Jest uses a concept called snapshot testing in order to do some integration testing.
import renderer from 'react-test-renderer';
import React from 'react';
import MenuSideBar from '../static/js/components/MenuSideBar.jsx';
test('Show Links in Main bakery site', () => {
const menuitems = ['Breakfast', 'Breakfast Ala Carte','Boxed Lunches', 'Entree Salads', 'Gourmet Trays and Appetizers', 'Main Selections', 'Pizzas'];
const tree = renderer.create(
).toJSON();
expect(tree).toMatchSnapshot();
});
You can use describe and it blocks to do bdd (behavior-driven development) style assertions.
it('should return a token when calling stripe api', (done) => {
stripe.tokens
.create(card)
.then(token => {
const {id} = token;
stripeCharge["charge"]["source"] = id;
expect(token["id"]).toBeDefined();
done();
})
.catch(err => {
if (err) {
winston.log('error', 'Stripe Token api error', {err})
done(err);
}
done();
});
});