How to build your own React boilerplate

Published Feb 19, 2018Last updated May 26, 2018
How to build your own React boilerplate

What is a Boilerplate?

In programming, the term boilerplate code refers to blocks of code used over and over again.

Let’s assume your development stack consists of several libraries, such as React, Babel, Express, Jest, Webpack, etc. When you start a new project, you initialize all these libraries and configure them to work with each other.

With every new project that you start, you will be repeating yourself. You could also introduce inconsistencies in how these libraries are set up in each project. This can cause confusion when you switch between projects.

This is where boilerplates come in. A boilerplate is a template that you can clone and reuse for every project.

The modular Javascript ecosystem simplifies application development through various libraries, frameworks, and tools. Boilerplates can be daunting if you don’t understand the fundamentals of their underlying components. Let’s learn about these basic building blocks while creating our own.

Click here for source on GitHub
I am using Webstorm, Git, NodeJS v8.9, NPM v5.6 and React v16. Fire up your favorite IDE, create a blank project and let's get started!

Git Repository: Setup

Create a project folder and initialize a git repo:

mkdir react-boilerplate && cd react-boilerplate
git init

You can connect this project to your own repo on GitHub using these instructions.

Readme File

Every project should contain a landing page with useful instructions for other developers. Let's create a README.md file under the project root with the following content:

# React-Boilerplate
This is my react-boilerplate

## Setup
npm install
npm run build
npm start

GitHub displays the contents of the readme file on the landing page for the project

Now, commit the above changes to git:

git add .
git commit -m "created readme"

At the end of each section, you should commit your code to git

Folder Structure

Create the following folder structure for your project:

react-boilerplate
    |--src
       |--client
       |--server

with the command:

mkdir -p src/client src/server

This folder structure is basic and will evolve as you integrate other libraries in the project.

Git Ignore

Once we build our project, there will be a few auto-generated files and folders. Let's tell git to ignore some of those files that we can think of ahead of time.

Create .gitignore under the root folder with the following content:

# Node
node_modules/

# Webstorm
.idea/

# Project
dist/

Comments in a .gitignore file are prefixed with #

Node Package Manager

The starting point for a node project is to initialize its package manager which creates a file called package.json. This file must be checked into git.

It generally contains:

  • A description of your project for NPM
  • List of references to all installed packages
  • Custom command line scripts
  • Configuration for installed packages

Go to your project root and type the following:

npm init

Fill out all the details and after you accept them, npm will create a package.json file that looks something like:

{
  "name": "react-boilerplate",
  "version": "1.0.0",
  "description": "Basic React Boilerplate",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/theoutlander/react-boilerplate.git"
  },
  "keywords": [
    "Node",
    "React"
  ],
  "author": "Nick Karnik",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/theoutlander/react-boilerplate/issues"
  },
  "homepage": "https://github.com/theoutlander/react-boilerplate#readme"
}

Static Content

Let's create a static HTML file src/client/index.html with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React Boilerplate</title>
</head>
<body>
    <div id="root">
        Welcome to React Boilerplate!
    </div>
</body>
</html>

Express Web Server

To serve the static file above, we need to create a web server in ExpressJS.

NPM v5 automatically saves installed packages under the dependencies section in package.json so the --save attribute is not necessary

npm install express

I would recommend following a file naming convention where file names are lower case and multiple words are separated by a dot. You will avoid running into case sensitivity issues across platforms as well as simplify naming files with multiple words across larger teams.

Create a file src/server/web.server.js and add the following code to host a web server via an express app and serve the static html file:

const express = require('express')

export default class WebServer {
  constructor () {
    this.app = express()
    this.app.use(express.static('dist/public'))
  }

  start () {
    return new Promise((resolve, reject) => {
      try {
        this.server = this.app.listen(3000, function () {
          resolve()
        })
      } catch (e) {
        console.error(e)
        reject(e)
      }
    })
  }

  stop () {
    return new Promise((resolve, reject) => {
      try {
        this.server.close(() => {
          resolve()
        })
      } catch (e) {
        console.error(e.message)
        reject(e)
      }
    })
  }
}

We have created a simple web server above with a start and stop command.

Click here to learn more about Promises

Startup File

Next, we need to create an index file which will initialize various high-level components. In our example, we're going to initialize the web server. However, as your project grows you can also initialize other components such as configuration, database, logger, etc.

Create a file src/server/index.js with the following code:

import WebServer from './web.server'

let webServer = new WebServer();
webServer.start()
  .then(() => {
    console.log('Web server started!')
  })
  .catch(err => {
    console.error(err)
    console.error('Failed to start web server')
  });

Babel

To run the above ES6 code, we need to transform it to ES5 first via Babel. Let's install Babel and the babel-preset-env dependency which supports ES2015 transpilation:

npm i babel-cli babel-preset-env --save-dev

Create a babel configuration file called .babelrc under the root and add the following details to it:

{
  "presets": ["env"]
}

The env preset implicitly include babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017 together, which means you can run ES6, ES7 and ES8 code.

Build Commands

Let's create commands to build the server and client components of the project and start the server. Under the scripts section of package.json , remove the line with the test command and add the following:

"scripts": {
    "build": "npm run build-server && npm run build-client",
    "build-server": "babel src/server --out-dir ./dist",
    "build-client": "babel src/client --copy-files --out-dir ./dist/public",
    "start": "node ./dist/index.js"
}

The build command above will create a dist/public folder under the root. The build-client command is simply copying the index.html file to the dist/public folder.

Starting up

You can run the babel transpiler on the code above and start the web server by using the following commands:

npm run build
npm start

Open your browser and navigate to http://localhost:3000. You should see the output of your static HTML file.

React Boilerplate

You can stop the web server by pressing <Ctrl> C

Test Harness: Jest

I cannot stress enough the importance of introducing unit tests at the beginning of a project. We're going to use the Jest Testing Framework which is designed to be fast and developer friendly.

First, we need to install jest and save it to development dependencies.

npm i jest --save-dev

Unit Tests

Let's add two test cases to start and stop the web server.

For test files, you should add a .test.js extension. Jest will scan the src folder for all files containing .test in the filename, you can keep your test cases under the same folder as the files they're testing.

Create a file called src/server/web.server.test.js and add the following code:

import WebServer from './web.server'

describe('Started', () => {
  let webServer = null

  beforeAll(() => {
    webServer = new WebServer()
  })

  test('should start and trigger a callback', async () => {
    let promise = webServer.start()
    await expect(promise).resolves.toBeUndefined()
  })

  test('should stop and trigger a callback', async () => {
    let promise = webServer.stop()
    await expect(promise).resolves.toBeUndefined()
  })
})

Test Command

Let's add an npm command to run the test under the scripts section of package.json. By default, jest runs all files with the word .test in their file name. We want to limit it to running tests under the src folder.

"scripts": {
...
    "test": "jest ./src"
...
}

babel-jest is automatically installed when installing Jest and will automatically transform files if a babel configuration exists in your project.

Let's run our tests via the following command:

npm test

React Boilerplate

Our application is set up to serve a static HTML file via an Express web server. We have integrated Babel to enable ES6 and Jest for unit testing. Let's shift our focus to the front-end setup.

React Setup

Install the react and react-dom libraries:

npm i react react-dom

Create a file called src/client/app.js with:

import React, {Component} from 'react'

export default class App extends Component {
    render() {
        return <div>Welcome to React Boilerplate App</div>
    }
}

Let's render the App via an index file under src/client/index.js with:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

ReactDOM.render(<App />, document.getElementById('root'))

Babel React

If you execute npm run build-client , you will get an error because we haven't told babel how to handle React / JSX.

React Boilerplate

Let's fix that by installing the babel-preset-react dependency:

npm install --save-dev babel-preset-react

We also need to modify the .babelrc config file to enable transpiling react:

{
  "presets": ["env", "react"]
}

Now, when you run npm run build-client , it will create app.js and index.js under dist/public with ES6 code transpiled to ES5.

Load Script in HTML

To connect our React App to the HTML file, we need load index.js in our index.html file. Don't forget to empty the text of the #root node since the React App will be mounted to it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React Boilerplate</title>
</head>
<body>
    <div id="root"></div>
    <script src="index.js"></script>
</body>
</html>

Run Server

If you fire up your web server and go to http://localhost:3000, you will see a blank page with an error in the console.

Uncaught ReferenceError: require is not defined

React Boilerplate

This is because Babel is just a transpiler. In order to support dynamically loading modules, we will need to install webpack.

Start by changing the build commands under scripts in package.json to build-babel:

"scripts": {
    "build-babel": "npm run build-babel-server && npm run build-babel-client",
    "build-babel-server": "babel src/server --out-dir ./dist",
    "build-babel-client": "babel src/client --copy-files --out-dir ./dist/public",
    "start": "node ./dist/index.js",
    "test": "jest ./src"
  }

Webpack

Webpack allows us to easily modularize our code and bundle it into a single javascript file. It is supported by numerous plugins and chances are that there's a plugin for almost any build task you can think of. Start by installing Webpack:

npm i webpack

By default, webpack looks for a configuration file called webpack.config.js , so let's create it in the root folder and define two entry points, one for the web application and the other for the web server. Let's create two config objects and export them as a collection:

const client = {
    entry: {
        'client': './src/client/index.js'
    }
};

const server = {
    entry: {
        'server': './src/server/index.js'
    }
};

module.exports = [client, server];

Now, let's specify where webpack will output the bundle and set the target build so that it ignores native node modules like 'fs' and 'path' from being bundled. For client, we will set it to web and for server we will set it to node.

let path = require('path');

const client = {
    entry: {
        'client': './src/client/index.js'
    },
    target: 'web',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist/public')
    }
};

const server = {
    entry: {
        'server': './src/server/index.js'
    },
    target: 'node',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    }
};

module.exports = [client, server];

Babel Loader

Before we can run webpack, we need configure it to handle ES6 and JSX code. This is done via loaders. Let's start by installing babel-loader:

npm install babel-loader --save-dev

We need to modify the webpack configuration to include babel-loader to run on all .js files. We will create a shared object defining the module section that we can re-use for both targets.

const path = require('path');

const moduleObj = {
    loaders: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loaders: ["babel-loader"],
        }
    ],
};

const client = {
    entry: {
        'client': './src/client/index.js',
    },
    target: 'web',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, '/pub')
    },
    module: moduleObj
};

const server = {
    entry: {
        'server': './src/server/index.js'
    },
    target: 'node',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: moduleObj
}

module.exports = [client, server];

For merging nested shared object, I would recommend checking out the Webpack Merge module

Excluding Files

Webpack will bundle referenced libraries which means everything that is included from node_modules will be packaged. We don't need to bundle external code as those packages are generally minified and they will also increase the build time and size.

Let's configure webpack to exclude all packages under the node_modules folder. This is easily accomplished via the webpack-node-externals module:

npm i webpack-node-externals --save-dev

Followed by configuring webpack.config.js to use it:

let path = require('path');
let nodeExternals = require('webpack-node-externals');

const moduleObj = {
    loaders: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loaders: ["babel-loader"],
        }
    ],
};

const client = {
    entry: {
        'client': './src/client/index.js',
    },
    target: 'web',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist/public')
    },
    module: moduleObj
};

const server = {
    entry: {
        'server': './src/server/index.js'
    },
    target: 'node',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: moduleObj,
    externals: [nodeExternals()]
}

module.exports = [client, server];

Update Build Command

Finally, we need to make changes to the scripts section under package.json to include a build command that uses webpack and to rename index.js to server.js for npm start as that's what webpack is configured to output.

"scripts": {
    "build": "webpack",
    "build-babel": "npm run build-babel-server && npm run build-babel-client",
    "build-babel-server": "babel src/server --out-dir ./dist",
    "build-babel-client": "babel src/client --copy-files --out-dir ./dist/public",
    "start": "node ./dist/server.js",
    "test": "jest ./src"
  }

Build Clean

Let's add a command to clean our dist and node_modules folders so we can do a clean build and ensure our project still works as expected. Before we can do that, we need to install a package called rimraf (which is the rm -rf command).

npm install rimraf

The scripts section should now contain

"scripts": {
...
"clean": "rimraf dist node_modules",
...
}

Clean Build w/Webpack

You can now successfully clean and build your project using webpack:

npm run clean
npm install
npm run build

This will create dist/server.js and dist/public/client.js under the root folder.

HTML Webpack Plugin

However, you may have noticed that index.html is missing. This is because previously we asked Babel to copy files that weren't transpiled. However, webpack isn't able to do that, so we need to use the HTML Webpack Plugin.

Let's install the HTML Webpack Plugin:

npm i html-webpack-plugin --save-dev

We need to include the plugin at the top of the webpack config file:

const HtmlWebPackPlugin = require('html-webpack-plugin')

Next, we need to add a plugins key to the client config:

const client = {
  entry: {
    'client': './src/client/index.js'
  },
  target: 'web',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist/public')
  },
  module: moduleObj,
  plugins: [
    new HtmlWebPackPlugin({
      template: 'src/client/index.html'
    })
  ]
}

Before we build the project, let's modify our HTML file and remove the reference to the index.js script because the above plugin will add that for us. This is especially useful when there are one or more files with dynamic filenames (for instance when files are generated with a unique timestamp for cache busting).

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React Boilerplate</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

Let's rebuild the project:

npm run clean
npm install
npm run build

And, verify that our existing tests are still running:

npm test

We have further updated the boilerplate to integrate React and Webpack, created additional NPM commands, dynamically referenced index.js in the HTML file, and exported it.

Enzyme Setup

Before we add a React test, we need to integrate Enzyme which will allow us to assert, manipulate and traverse react components.

Let's start by installing enzyme and enzyme-adapter-react-16 which is required to connect enzyme to a project using react v16 and above.

enzyme-adapter-react-16 has peer dependencies on react, react-dom, and react-test-renderer

npm i --save-dev enzyme enzyme-adapter-react-16 react-test-renderer

Create a file src/enzyme.setup.js with the following content:

import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

Enzyme.configure({
    adapter: new Adapter()
})

We need to configure jest to use src/enzyme.setup.js in package.json by adding the following section under the root object:

{
...
"jest": {
    "setupTestFrameworkScriptFile": "./src/enzyme.setup.js"
  }
...
}

React Component Test

Let's test the App Component and ensure that it renders the expected text. In addition, we will take a snapshot of that component so we can ensure that its structure hasn't changed with every test run.

Click here to learn more about snapshot testing

Create a test case under src/client/app.test.js with the following content:

import App from './app'
import React from 'react'
import {shallow} from 'enzyme'

describe('App', () => {
  test('should match snapshot', () => {
    const wrapper = shallow(<App/>)

    expect(wrapper.find('div').text()).toBe('Welcome to React Boilerplate App')
    expect(wrapper).toMatchSnapshot()
  })
})

If we run this test now, it will pass with a warning:

React Boilerplate

Let's fix that by installing a polyfill called raf:

npm i --saveDev raf

And changing the jest configuration under package.json to:

{
...
"jest": {
    "setupTestFrameworkScriptFile": "./src/enzyme.setup.js",
    "setupFiles": ["raf/polyfill"]
  }
...
}

Now, you can verify that all our tests are passing:

npm test

React Boilerplate

After running the react test, you will notice a new file at src/client/snapshots/app.test.js.snap. It contains the serialized structure of our react component. It must be checked into git so it can be used to compare against the dynamically generated snapshot during a test run.

Final Run

Let's start the web server one more time and navigate to http://localhost:3000 to ensure everything works:

npm start

React Boilerplate

I hope this article has given you insights into streamlining the process of starting a new project from scratch with Express | React | Jest | Webpack | Babel. It is a good idea to create your own reusable boilerplate so you understand what goes on under the hood and at the same time get a head-start when creating new projects.

We have barely scratched the surface and there is a lot of room for improvement to make this boilerplate production ready.

Here are some things for you to try:


If you would like to learn more about the react ecosystem, I would highly recommend taking The Complete React Web Developer Course by Andrew Mead.

Discover and read more posts from Nick Karnik
get started