Fancy Hello World
A simple Node app ilustrating:
- Universal JavaScript. Routing & Rendering with shared components, shared store, & shared routes.
- State managed by Redux.
- Standard ES6 modules using Babel + webpack.
- React + JSX + ES7 object spread via Babel.
- Express 4.x.
- Useful scripts and conventions for app development.
Fork of the Universal React Boilerplate by cloverfield-tools
Link to the original repo:
Why I made this project:
- Even though Universal React Boilerplate is deprecated in favor of Next.js, I think I still find this project helpful for having a starting point for my apps and also to show developers how to wire a project with server side rendering, hot reloading, babel, webpack, react and redux.
- To include hot reloading
- To use docker
Getting Started
We're using an ES6 template string for the page skeleton + React to render the actual UI into the root
div.
The React render happens on both the server and the client using shared code. React components are written in class-free style using pure components wherever possible.
yarn install
yarn run build:dev
yarn run dev
Now the app should be running at http://0.0.0.0:3000/
Universal JavaScript
Universal JavaScript (aka "isomorphic JavaScript") means that it's designed to run a lot of the same code on both the client and the server. Typically that includes a lot of rendering and domain logic.
There are many advantages to building apps this way, but the primary advantages are:
- Cross-functional teams. Since everything is written in JavaScript, it's easier to build teams who know how to work on both the client and server sides of the app.
- Write once, run everywhere. With the exception of a few library substitutions and browser polyfills, the code is shared, which means you have to write about half the code you'd write working on a non-universal app.
- More productive developers. Since the app is more consistent across the stack, there's no context switching when you need to maintain application behavior on both sides of the stack. Write the behavior once, and you're done. Context switching slows developers down significantly.
Tech stack
The universal boilerplate uses standard JavaScript modules to author all of the code. All open-source modules are sourced from npm
.
What's inside?
There are some concerns that legitimately belong only on the server, or only on the client, so there are client/
and server/
directories for code that is specific to one or the other. Shared code goes in shared/
:
source/shared
- Shared code.source/client
- For browser-only code.source/server
- For server-only code.
Index
The server/index
route serves dynamic content. Static assets are served from the build
folder using express.static
.
Scripts
Some of these scripts may require a Unix/Linux environment. OS X and Linux come with appropriate terminals ready to roll. On Windows, you'll need git installed, which comes with Git Bash. That should work.
The package.json
file comes with the following scripts that you may find useful:
yarn run dev
runs a client-only devserveryarn run watch
runs a dev console that reports lint and unit test errors on saveyarn run lint
lints the code under the project folder with eslintyarn run test
runs the commit tests and some smoketestsyarn run build
rebuilds the client and the serveryarn run deploy
creates a docker imageyarn run start
starts the created docker image in a containeryarn run stop
stops and removes the containers that run the imageyarn run test:e2e
runs nightwatch against the docker containeryarn run all
runs the deployment pipeline (fancy way to say all of the commands avove)
To run a script, open the terminal, navigate to the boilerplate directory, and type:
yarn run <name of script>
Start
Start the dev server.
yarn run dev
Log messages will be written to the console (stdout) in JSON format for convenient queries using tools like Splunk. You should be able to pipe the output to a third party logging service for aggregation without including that log aggregation logic in the app itself.
Developer feedback console:
yarn run watch
The dev console does the following:
- Checks for syntax errors with
eslint
using idiomatic settings from.eslintrc
- Runs the unit tests and reports any test failures.
- Watches for file changes and re-runs the whole process.
Requiring modules
To require modules relative to the app root, just put them in source
and require them just like you would require a module installed by npm. For example, if you had a file called source/routes/index.js
you can require it with:
import routes from 'routes';
This is a lot cleaner than using relative paths and littering your code with stuff like ../../../module/path/module.js
.
This requires the NODE_PATH
environment variable to be set to source
. For example from the package.json
:
scripts: {
"test": "NODE_PATH=source babel-node source/test/index.js",
}
We also need to tell webpack configs (located in the project root) about the source path:
resolve: {
modules: [
'node_modules',
path.join(__dirname, 'source'),
]
}