The Isomorphic Material Relay Starter Kit (IMRSK) project started as an off-shoot of multiple projects and boilerplates we use at Code Foundries and is developed with the help of the contributors. It was an attempt to provide and organize a well thought out starting point for future projects.
Since then it has added on several features and support for React Native, which makes it into a starter kit for web and native mobile applications with a common backend (native still without authentication and samples). It also contains samples of techniques that tie all the underlying technologies together. Baseline functionality like user log in, authentication, etc. is included.
How to try | Link |
---|---|
Live Demo on Heroku | http://isomorphic-material-relay.herokuapp.com/. Configured with in-memory data store. This is a free dyno, so give it some time to spin up. |
Live Demo on Open Shift | http://app-imrsk.rhcloud.com/. Configured with Cassandra data store. Under active development, might be down. |
Run locally | Local Setup. MacOS X. |
Run on Heroku | Heroku Setup. With Heroku-specific troubleshooting instructions. |
The following documents explain in detail certain aspects of this repository in depth:
- Cassandra, meet Relay. Relay, meet Cassandra. Explanation of how Cassandra and Express GraphQL work together.
- Isomorphic Server Variables. Using variables and settings in an isomorphic application - client rendering, server rendering.
- SEO Using Isomorphic Application With Relay and Helmet. How to make SEO-friendly pages with React, Relay and Helmet.
Feature | Details |
---|---|
Single Page Application | The IMRSK is a Single Page Application. It has all the advantages that come with SPA, while using isomorphism and the Relay features allows to offset practically all negatives that come with this approach. |
React and React Native | The use of both React (for the web) and React Native (for mobile apps), connected to the same GraphQL backend, allows for maximum reuse of code and developer skills. |
Isomorphism | The kit is designed to be fully isomorphic. This allows for very quick rendering when the users first access a page in the application. All content is designed to be accessed in an isomorphic way allowing the use and sharing of links to any part of the SPA. |
SEO | The combination of isomorphism with the use of the react helmet allows all the content in the application to be SEO ready. Samples will be added to the kit later. |
ES6, ES7 | The IMRSK uses features of ES6 and ES7 extensively. All react components are implemented as ES6 classes. |
JWT Tokens | JWT tokens are used for authentication. They are stored in server-only cookies which, together with the use of GraphQL, provides a fair amount of protection against several possible attacks. |
CSRF Tokens | Tokens are passed with each GraphQL request to prevent Cross Site Request Forgery exploits. |
HTTP Only cookies | The HTTP only cookies are currently the safest way to handle authentication in a web application. The JWT tokens are stored in HTTP only cookies making it harder for attackers to access. |
Material Design | Expanding upon the "card" motifs that debuted in Google Now, Material Design makes more liberal use of grid-based layouts, responsive animations and transitions, padding, and depth effects such as lighting and shadows. |
Responsive Design | Mainly through the features of the Material UI library, the examples in IMRSK work well on different form factors, ranging from desktop browsers to mobile phones. |
Hot Reload | The webpack development server supports hot reload when components are changed, greatly improving developer productivity. The IMRSK is configured with hot reload. |
Built for speed | The starter kit is configured to use established practices for optimizing speed like caching and compression. This entails certain requirements about how it is used, read in the Speed and Building section below. |
Units | The examples are broken into semi-independent units containing all client and server side code necessary. Adding and removing units can be done fairly easily without affecting the application in a major way. |
Cassandra/In Memory | The starter kit can be used with both Cassandra-based data storage, as well as in-memory structures. The latter are right now used only for testing, but can be later used for setting up mock tests cases for unit and regression testing. |
User management | The starter kit contains code for managing user accounts. The different parts of the application can be accessed by anonymous or authenticated users, or both. |
Technology | Description |
---|---|
React | Library for building SPA. |
React Native | React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. |
Material UI | Library for implementing Material Design in React. All user interface in this kit is built exclusively with Material UI components. |
material-ui-country-flags | Library with flag icons for Material-UI, used for the language selector. |
Relay | A Javascript framework for building data-driven react applications. |
GraphQL | A query language created by Facebook in 2012 for describing the capabilities and requirements of data models for client‐server applications. |
Express GraphQL | A Node.js express library that allows the creation of GraphQL servers. |
Isomorphic Relay | Adds server side rendering support to React Relay. IMRSK fully utilizes this library, while waiting for facebook/relay#589. The eventual goal is to have full isomorphism with authentication. |
Data Loader | Generic utility to be used as part of your application's data fetching layer to provide a consistent API over various backends and reduce requests to those backends via batching and caching. |
Apache Cassandra | The right choice when you need scalability and high availability without compromising performance. Linear scalability and proven fault-tolerance on commodity hardware or cloud infrastructure make it the perfect platform for mission-critical data. Cassandra's support for replicating across multiple datacenters is best-in-class, providing lower latency for your users and the peace of mind of knowing that you can survive regional outages. |
JWT | JSON Web Tokens is an industry standard RFC 7519 method for representing claims securely between two parties. |
React Helmet | Reusable React component will manage all of your changes to the document head with support for document title, meta, link, script, and base tags. |
Flow | Static type checker, designed to find type errors in JavaScript programs. |
ESLint | Pluggable linting utility for JavaScript and JSX. |
Babel | Compiles ES6/ES7 to ES5. Allows using features from ES6 and ES7 today. |
Webpack | Bundles npm packages and the application Java Script into a single file. Includes hot reloading via react-transform-hmr. Also, Webpack can bundle any required CSS. |
Node.js | Event-driven, non-blocking I/O runtime based on JavaScript that is lightweight and efficient. |
npm Scripts | Glues all this together in a handy automated build. |
- Use of HTTP Only cookies Why this is the best place is explained in Where to Store Your JWTs - Cookies vs HTML5 Web Storage.
In order to set up the project on heroku, perform the following steps:
- Install Heroku Toolbelt.
- Clone from github
git clone https://github.com/codefoundries/isomorphic-material-relay-starter-kit
. - Create an app
heroku create
. - Specify JWT secret using
heroku config:set JWT_SECRET=xyzxyzxyz
where you replace the secret value with a secret of your choosing. Verify that the value is set withheroku config
. - Specify variables for Cassandra connection points despite the fact that they are not used:
heroku config:set CASSANDRA_CONNECTION_POINTS=na
andheroku config:set CASSANDRA_KEYSPACE=na
. - Specify object persistence in memory:
heroku config:set OBJECT_PERSISTENCE=memory
. - Specify anomymous user auth token using
heroku config:set ANONYMOUS_USER_AUTH_TOKEN=xyzxyzxyz
where you replace the token value with a token of your choosing. - Verify that the above values are set with
heroku config
. - Deploy the app
git push heroku master
.
For more information refer to excellent Getting Started with Node.js on Heroku - Deploy the app. I do not have an available free Cassandra dyno on Heroku so I can not test how to configure Cassandra.
heroku run bash
is your friend in need who is a friend indeed.
Whenever, when updating Heroku, an 'UNMET DEPENDENCY' message similar to the one below is displayed:
remote: [email protected] /tmp/build_xxxxxxxxxxxx
remote: ├─┬ [email protected]
remote: │ ├── [email protected]
remote: │ └── UNMET PEER DEPENDENCY react@^0.14.3
remote: ├── UNMET DEPENDENCY react@^0.14.6
remote: ├── [email protected]
remote: └─┬ [email protected]
remote: └── [email protected]
Try the following troubleshooting step from the Heroku troubleshooting manual: Each time you run npm install
, npm leaves packages that meet your semver requirements untouched. That’s why an npm install
today may lead to a different tree than the npm install
you ran yesterday, even if your package.json
didn’t change.
Therefore, it’s a good practice to periodically clear node_modules
and reinstall from scratch to ensure that your package.json
dependencies are still valid:
$ rm -rf node_modules
$ npm install --quiet --production
$ npm start
In fact, those are essentially the commands that Heroku runs when we build and launch your project. If they work locally, you’re likely to be cloud-ready.
If this does not work, running
npm shrinkwrap
will generate the npm-shrinkwrap.json
file, which seems to resolve the above problem in most cases.
- Install Node.js.
- Install Git.
- Install Apache Cassandra.
- Make sure that Node.js is at least version 5.0 and NPM is at least version 3.
We have only tested this running on MacOS. I am copying the instructions for other operating systems from the React Slingshot. We have not had a chance to test them and would appreciate help with that. If you have done that please open an issue with the results whether successful or not, and feel free to PR to update this document.
Run this to increase the limit on the number of files Linux will watch. Here's why.
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
- Install Python 2.7. Browser-sync (and various other Node modules) rely on node-gyp, which requires Python on Windows.
- Install C++ Compiler. Visual Studio Express comes bundled with a free C++ compiler. Or, if you already have Visual Studio installed: Open Visual Studio and go to File -> New -> Project -> Visual C++ -> Install Visual C++ Tools for Windows Desktop. The C++ compiler is used to compile browser-sync (and perhaps other Node modules).
In order to set up the project locally, perform the following steps:
- Clone from github.
git clone https://github.com/codefoundries/isomorphic-material-relay-starter-kit
. - Install node packages.
npm install
. - Perform initial setup.
npm run setup-local
. - Specify JWT_SECRET by modifying the
.env
file. This step can be skipped if you do not care about the actual security and simply want to get the project running. - Start the server.
npm run start
. - The application is available at:
http://localhost:4444
.
Task | Details |
---|---|
postinstall |
Called by NPM after npm install . Configures to use memory data access, re-builds the GraphQL schema and performs a webpack build. |
setup-local |
Sets up the IMRSK for use on local dev machine. |
setup-cassandra-init |
Drops (if it exists), creates and initializes with sample data a local Cassandra database called imrsk . |
config-da-memory |
Configure IMRSK for using in-memory data structures. |
config-da-cassandra |
Configure IMRSK for using Cassandra for persistence. |
Task | Details |
---|---|
build-schema |
Rebuilds GraphQL schema from the JavaScript definition. |
build-mui-icon-list |
Re-builds list of icons based on the ones available in Material-UI. If if( key > 50 ) return; is uncommented, all icons will be generated. It becomes quite the page and webpack voices objections, but it works. |
build-webpack |
Runs a webpack build in order to run in production mode. Created and populates `public/assets/{Version}``. |
Task | Details |
---|---|
start-webpack |
Starts the webpack development server, responsible for asset compilation and hot reload. |
start-dev |
Starts the application server in development mode. |
dev |
Starts the task at the same time: start-webpack and start-dev . |
start |
Run in production mode. |
Two separate servers need to be started. The first one is the actual application in development mode. The second server is the webpack server which is to be run at all times for hot replace
- Start application HTTP and Webpack server:
npm run dev
.
To open the app:
- Navigate to
http://localhost:4444
, unless you specified a different port.
The following environment variables can be used to control the server:
Variable Name | Description |
---|---|
PORT | Port for serving the SPA web application and API. |
HOST | Host for for serving, for instance 127.0.0.1 . |
PUBLIC_URL | URL through which browsers and other clients can access the server - isomorphic pages, public, GraphQL. Optional. Should not be empty. Example: https://example.com |
ANONYMOUS_USER_AUTH_TOKEN | Secret passed by server rendered to GraphQL server telling it to trust the auth_token without requiring the user_auth_token in the header to be correct. Instead it constains this very secret. |
JWT_SECRET | Secret used for JWT tokens. |
OBJECT_PERSISTENCE | whenther to use memory or cassandra as persistence layer |
CASSANDRA_CONNECTION_POINTS | Cassandra connection point comma separated list. localhost if on the same machine. |
CASSANDRA_KEYSPACE | Cassandra keyspace/database. |
CASSANDRA_USER | Optional Cassandra username. |
CASSANDRA_PASSWORD | Optional Cassandra password. |
They can be set in the .env
file in the root of the project. Example.env
in
the documents folder contains an example of such file.
Naming conventions are used wherever possible. The following tags are used to comprise names of files:
Tag | Description |
---|---|
{Entity} |
Name of entity in the data store, like User, ToDo item, etc. |
{Mutation} |
Indicates type of mutation applied to an entity, like add, delete, update, list_delete, etc. |
{Version} |
Version of the project, as specified in package.json , like 0.6.3. |
{Unit} |
Name of unit, for instance imrsk-example-compendium . |
Below is the list of the main files and folders for this project. Asterisk on the right means link into the repository for quick viewing.
Folder/File | Description | |
---|---|---|
app/ |
Root for the react-native app. | * |
data/ |
Methods and data access functions. | * |
data/lib/ |
Wrappers around data loader, cassandra driver and in-memory persistence. | * |
data/lib/CassandraClient.js |
Promisified Cassandra client. | * |
data/lib/CassandraOptions.js |
Options for connecting to Cassandra used by client and logger. | * |
data/lib/ObjectManagerBase.js |
Manage objects for GraphQL resolvers using DataLoader and Cassandra or in-memory persistence. | * |
data/lib/ObjectPersisterCassandra.js |
Persists objects in Cassandra. | * |
data/lib/ObjectPersisterMemory.js |
Persists objects in memory. | * |
data/model/ |
Models | * |
data/model/{Entity}.js |
Model for {Entity}. Default class for that entity is exported. | |
data/ObjectManager.js |
Object manager instance with initialization for all types used by GraphQL server. | * |
doc/ |
Misc. documentation. | * |
doc/example.env |
Example of a .env file. Also copied into \.env in setup-local script. |
* |
graphql/ |
Holds the elements of the GraphQL schema. | * |
graphql/type/ |
System types. | * |
graphql/type/MutationType.js |
Type that includes all the mutations. | * |
graphql/type/QueryType.js |
Query type that resolves nodes to entities. | * |
graphql/type/ViewerType.js |
Current user and entry point for any information retrieved. | * |
graphql/NodeInterface.js |
The main node interface. | * |
graphql/schema.graphql |
Human readable representation of the schema. Generated by build-schema . |
* |
graphql/server.js |
The GraphQL Express server. | * |
graphql/schema.js |
Entry point for the schema, points at the query type and the mutation type. | * |
graphql/schema.json |
Schema in JSON format. Must exist for build-schema to run, but is re-generated by it. |
* |
public/ |
This folder is served as root of the website. | * |
public/assets/ |
Assets generated by webpack. | |
public/assets/{Version}/app.css |
CSS assets compiled by WebPack. Not much to see. | |
public/assets/{Version}/app.js |
All the nice ES5-compliant JavaScript for the SPA. | |
scripts/build-mui-icon-list.js |
Rebuilds the list of Materual-UI icons. Modify this file to control how many icons are displayed. | * |
scripts/build-schema.js |
Rebuilds the GraphQL schema files. Must be run when the schema is modified. | * |
scripts/cassandra-init.cql |
CQL Script for creating and seeding the Cassandra database. | * |
server/ |
The Node.js server serving isomorphic content, GraphQL, public files and authentication requests. | * |
server/auth.js |
Authentication service, verifies user name and password and creates JWT tokens. | * |
server/credentials_check.js |
Functions for verifying issued JWT tokens and protecting against CRSF. | * |
server/server.js |
Main script. Loads all other servers. | * |
units/ |
Units included in application, including all elements of the stack. | * |
units/_all/ |
Files with includes summarizing certain includes of all units. | * |
units/_all/_mutations.js |
GraphQL Mutations from all units to be included in the mutation type. | * |
units/_all/_ViewerFields.js |
Fields included in the viewer type from all units. | * |
units/{Unit}/ |
A single unit. See table below for per-unit structure | * |
webapp/ |
Root for the entire web application. | * |
webapp/components/ |
All the JSX components used by the web app. | * |
webapp/mui-themes/ |
Material-UI themes. | * |
webapp/mui-themes/active-theme.js |
Points to the theme that is currently active. | * |
webapp/scripts/ |
Scripts used by the client. | * |
webapp/styles/ |
Styles used by the client. | * |
webapp/styles/main.css |
Example style included in the app. Currently not used. | * |
webapp/app.js |
Starts the client-side SPA using data generated during server rendering. | * |
webapp/renderOnServer.ejs |
Template for the HTML served by the isomorphic server rendered. | * |
webapp/renderOnServer.js |
Performs server-side rendering. | * |
webapp/routes.js |
Routes in a data structure consumed both by express router and react router. | * |
webapp/server.js |
Server for the web app. | * |
The structure of each unit is as follows:
Folder/File | Description | |
---|---|---|
/ |
Root of unit at relative path units/{Unit}/ |
* |
graphql/ |
All files included in the express-graphql server schema. | * |
graphql/type/ |
Object types. | * |
graphql/type/*Connection.js |
Connection between two types. | |
relay/ |
All Relay client side files shared between react DOM and native. | * |
webapp/ |
React DOM files. | * |
Material-UI provides powerful means for customizing the colors and the overall look of the application. The IMRSK uses a custom theme as indicated in ./webapp/mui-themes/active-theme.js. The active-theme.js
file itself simply imports and re-exports one of the themes in the folder. By convention, all components will be importing active-theme.js
whenever theme information is required on the component level. Here is an example of some of the settings from the webapp/mui-themes/grayBlue.js
primary1Color: Colors.blue500,
primary2Color: Colors.blue700,
primary3Color: Colors.lightBlack,
accent1Color: Colors.purpleA200,
accent2Color: Colors.blueGrey100,
accent3Color: Colors.blueGrey500,
textColor: Colors.darkBlack,
alternateTextColor: Colors.white,
canvasColor: Colors.white,
borderColor: Colors.grey300,
disabledColor: ColorManipulator.fade(Colors.darkBlack, 0.3),
pickerHeaderColor: Colors.blue500,
This project configured to use compression on all content, and caching on the static content. This delivers spectacular results. The numbers below were obtained using Google Chrome, development tools throttling and cache disabling, on a 2011 series Macbook Pro. The test is performed on the main page. Instance deployed on Heroku was used.
Network Speed | Initial load (no cache) | Subsequent load (cached content) |
---|---|---|
On Regular 2G (250 kb/s) | 7.3 sec | 1.4 sec |
On Regular 3G (750 kb/s) | 3.1 sec | 1.1 sec |
In both cases the UI becomes visible in less than a second. The wait time is to get all the JavaScript loaded in order to continue working as an SPA.
While this is a desirable level of performance, it is important not to forget that caching the static content means caching the SPA code too. The assets generated with webpack are placed into a sub-folder bearing the version of the package as name. Make sure to increase the version number in package.json
every time you deploy changes to production.
If you add other static content, you might want to revisit how caching of static content is implemented.
This project contains a boilerplate with several simple code examples. It consists of modified versions of the following projects:
- Facebook Relay Starter Kit TODO example. Implementation of the TODO MVC using Relay. In this kit the TODO example has been re-implemented with material UI.
- Facebook Relay Todo MVC example. Implementation of the TODO MVC using Relay and React Native.
- Isomorphic react-router-relay TODO example. The Facebook TODO example in this project has been modified to support isomorphism.
- Xpepermint's isomorphic-react-relay-boilerplate. The project organization was initially borrowed from this project, although it has since diverged.
- coryhouse's react-slingshot. Ideas about he documentation are borrowed from this project.
- ryancole's league. The organization of the GraphQL schema is borrowed from this project.
- itayadler's cassandra-paginating-static-columns. The general approach to working with Cassandra was initially borrowed from this project. However, over time the implementation became very different.
Examples from other open source projects have also been incorporated.
If you like to help, please review the list of items where help is needed.