React App with Node Backend

Development Environment Setup with Webpack

The purpose of this post is to show how I am used to configuring my development environment when I need to create a React App based on a Node Backend which is simultaneously an API and a Web Server. The problematic is to be able to automatically refresh my React App without having to rebuild this one and restart the backend manually during the development phase. If I update a file, I want the code to be recompiled without having to run the full build and to refresh my browser.

Source code

The whole source code of this article is in my GitLab repository.

Prerequisites

We assume the following versions are installed on my computer.

  • My favorite IDE

    • Visual Code Studio : 1.23.0
  • In global mode

    • node –version : v8.11.1
    • npm –version : 5.6.0

Creation of the project structure

Normally in a team development process I am used to setting two different GIT repositories:

  • one for the React App
  • one another for the Node Server

But for this post, and because I’m the only one developer working on this project, I set up a single GIT repository.
Assuming the root directory is my GIT repository react-node-webpack, I create the following structure

  • client for the React frontend
  • server for the Node backend

    └── react-node-webpack
    ├── client
    └── server
    

I’m going to look after the frontend setting up before creating the backend.

Setup the client

In client directory.

NPM

Do a npm init -y to create a default package.json file which should look like the following

{
  "name": "client",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

WEBPACK

> npm install webpack webpack-cli --save-dev

Note that installed versions are

  • webpack@4.7.0
  • webpack-cli@2.1.2

REACT modules

As usual, I install React by adding the 2 following modules

> npm install react react-dom --save

Moreover, I install prop-types but it’s not mandatory

> npm install prop-types --save

Note that installed versions are

  • react@16.3.2 (open-source javascript library for definig UI)
  • react-dom@16.3.2 (glue between the previous React library and DOM)
  • prop-types@15.6.1 (runtime type checking for React props and similar objects)

BABEL modules

The React components and my ES6 code need to be transpiled in browsers compatible javascript.
ES6 is also named es2015 by opposition to the old javascript ES5. To do this transformation, Webpack need to load the files and apply the Babel transpiling rules on them.

So I need to install

  • babel-loader to load ES6/React files and to transform them in ES5 files thanks to
  • babel-core and its two presets

    • babel-preset-env (transpiles ES6, ES7 or ES8 to ES5)
    • babel-preset-react (transpiles JSX to javascript)

      > npm i babel-loader babel-core babel-preset-env babel-preset-react --save-dev
      

Note that installed versions are

  • babel-loader@7.1.4
  • babel-preset-react@6.24.1
  • babel-preset-env@1.6.1
  • babel-core@6.26.3

Other WEBPACK modules

  • for css files

    > npm i style-loader css-loader --save-dev
    
  • to create the HTML file to serve the React bundle

    > npm install html-webpack-plugin --save-dev
    
  • to be able to run the React App in browser during the development

    > npm i webpack-dev-server --save-dev
    

Note that installed versions are

  • style-loader@0.21.0
  • css-loader@0.28.11
  • html-webpack-plugin@3.2.0
  • webpack-dev-server@3.1.4

React App: very simple version

First, I create a very simple version of my frontend and I will work on the webpack configuration. Then I will complexify the code to show the automatic refresh capability of webpack-dev-server.

What I am going to do

  1. create a class App.jsx which will contain the true application with its very usefull functionnalities
  2. create an entry point index.jsx which will instanciate the App class and inject this one in DOM

First I create a components directory in client.

App.jsx

Here is my Simple Page App. For now, it does not do much.
In components, create

import React from "react";

export default class App extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
                <div>React App</div>
                <div>
                    <div>Navigation</div>
                    <div>Display area</div>
                </div>
                <div>&copy; Ent Wickler</div>
            </div>
        );
    }
}

index.jsx

My entry point which is the glue between the DOM and the React SPA.
In client, create

import React from "react";
import ReactDom from "react-dom";

import App from "./components/App";

ReactDom.render(
  <App />,
  document.getElementById('app')
);

Webpack setting

webpack.config.js

What I am setting

  1. the entry of my React App, it is my previous entry point index.jsx
  2. the output is where Webpack creates the resulting bundle file
  3. the HtmlWebpackPlugin allows to define the index.html file needed to start the frontend in the browser. It is based on the template file index.htm.
  4. the rules of the modules for JS, JSX and CSS files loading

In client, create

var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',

  entry: [
    path.resolve(__dirname, "./index.jsx")
  ],

  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "bundle.js"
  },

  plugins: [
    new HtmlWebpackPlugin({
      title: 'React App with Node Backend',
      filename: 'index.html',
      template: 'index.htm'
    })
  ],

  module: {

    rules: [
      /* both JS and JSX files are processed so I could name a JSX file with ".js" suffix*/
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env', 'react']
          }
        }
      },

      /* css */
      {
        test: /\.css$/,
        use: [
          { loader: "style-loader" },
          { loader: "css-loader" }
        ]
      }

    ]

  },

  /* our './myComponent' requires or imports are equivalent to 'myComponent.jsx' */
  resolve: {
    extensions: ['*', '.js', '.jsx']
  }
}

html template : index.htm

In client, create

<!DOCTYPE html>
<html>

<head>
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
</head>

<body>
  <div id="app"></div>
</body>

</html>

Test

Start webpack-dev-server

In client, do

> .\node_modules\.bin\webpack-dev-server

Then webpack-dev-server compiles the code and runs it in a server which listens on the 8080 port.

In the browser

Then typing this URL http://localhost:8080, I should see the following ugly result

Adding css

In `components add the following file App.css

body {
  margin: 0;
  font-size: 1.5em;
}

.container {
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.content {
  flex: 1;
  display: flex;
  flex-direction: row;
}

.display {
  background-color: #DCDCDC;
  flex: 1;
}

.header {
  background-color: #FFFFFF;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  flex-direction: row;
}

.footer {
  background-color: #808080;
}

.nav {
  background-color: #F5F5F5;
}

.item {
  margin: 5px;
  padding: 5px;
  border: solid 1px grey;
  text-align: center;
}

Then I modify the App.jsx file to include the css

import React from "react";
import './App.css';

export default class App extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div className="container">
                <div className="header item">React App</div>
                <div className="content">
                    <div className="nav item">Navigation</div>
                    <div className="display item">Display area</div>
                </div>
                <div className="footer item">&copy; Ent Wickler</div>
            </div>
        );
    }
    
}

I do nothing else and when i look at my browser, it is magic !.
webpack-dev-server has detected the files changes and has refreshed the browser automatically.
No need to restart the build.

React App: with usefull content

Let’s go further.

Add a new class: a clock

No scoop, it is a well known class that I just have modified a bit.
In components, I create the following Clock.jsx file

import React from "react";
import PropTypes from 'prop-types';

export default class Clock extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            time: this.time()
        };
    }

    time() {
        let options = {
            hourCycle: 'h24',
            year: 'numeric', month: 'numeric', day: 'numeric',
            hour: 'numeric', minute: 'numeric', second: 'numeric',
            timeZone: this.props.timeZone
        };

        try {
            let today = new Date();
            return today.toLocaleDateString([], options);
        }
        catch (e) {
            return e.message;
        }
    }

    tick() {
        this.setState({
            time: this.time()
        });
    }

    componentDidMount() {
        this.intervalID = setInterval(
            () => this.tick(),
            1000
        );
    }

    componentWillUnmount() {
        clearInterval(this.intervalID);
    }

    render() {
        return (
            <div className="item">
                <div>{(this.props.children) ? this.props.children : this.props.timeZone}</div>
                <div>{(this.props.time) ? this.props.time : this.state.time}</div>
            </div>
        );

    }

}

Clock.defaultProps = {
    timeZone: "Europe/Paris"
};

Add the Clock class to my App

I modify the App.jsx like this

import React from "react";
import Clock from './Clock';

import './App.css';

export default class App extends React.Component {

  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="container">
        <div className="header item">
          <Clock />
          <Clock><b>Lyon</b></Clock>
          <Clock time={new Date().toLocaleDateString([],
            {
              hourCycle: 'h24', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZone: "Europe/Paris"
            })}><i>Fixed/Init_Time</i></Clock>
          <Clock timeZone="Europe/London" />
          <Clock timeZone="Europe/Berlin" />
          <Clock timeZone="America/New_York" />
          <Clock timeZone="Asia/Tokyo" />
          <Clock timeZone="Asia/Unknown_City" />
        </div>
        <div className="content">
          <div className="nav item">React App</div>
          <div className="display item">Display area</div>
        </div>
        <div className="footer item">&copy; Ent Wickler</div>
      </div>
    );
  }

}

Look at the result

The browser refrehes itself and the clocks too !

Project structure at this point

Note there is no dist directory because webpack-dev-server build the bundle and run it in memory.

└── react-node-webpack
    ├── client
    |   ├── components
    |   |   ├── App.css
    |   |   ├── App.jsx
    |   |   └── Clock.jsx    
    |   ├── node_modules
    |   ├── index.htm
    |   ├── index.jsx
    |   ├── package.json
    |   └── webpack.config.js
    └── server

It’s time to create the backend server.

Node Backend

Initialisation

In the server directory:

  1. we do a npm init -y to initialize our directory
  2. we install express with npm install express --save

Note the installed version

  • express@4.16.3

Create the server

In server, create the index.js file like this

'use strict';

var express = require('express');
var app = express();
app.use(express.urlencoded({'extended': true}));
var apiRoutes = express.Router();
app.use('/api', apiRoutes);

/*
http://localhost:3000/api/date?timezone=America/New_York 
return : {"localeDateString":"2018-5-5 16:07:41"}
*/
apiRoutes.get('/date', function (req, res) {
    let options = {
        hourCycle: 'h24',
        year: 'numeric',
        month: 'numeric',
        day: 'numeric',
        hour: 'numeric',
        minute: 'numeric',
        second: 'numeric',
        timeZone: req.query.timezone
    };
    let result;

    try {
        let today = new Date();
        result = today.toLocaleDateString([], options);
    } catch (e) {
        result = e.message;
    }

    res.status(200).json({
        'result': result
    });
    return;
});

app.listen(3000, function () {
    console.log('Node Backend started on port [%d]', 3000);
});

Test

In server start the backend

> node index

Then in the browser type http://localhost:3000/api/date?timezone=europe/paris.
You should see the following result {"result":"2018-5-5 22:19:28"}.

Automatic refresh of the backend

Like with the React App and the webpack-dev-server, I want my Backend be automatically reloaded if I update a file.
For that, I install nodemon

> npm install nodemon -g

Note the installed version

  • nodemon@1.17.4

In server start the backend in watching mode

> nodemon index

Then if I modify the inde.js file with replacing result by localeDateString.

'use strict';

var express = require('express');
var app = express();
app.use(express.urlencoded({'extended': true}));
var apiRoutes = express.Router();
app.use('/api', apiRoutes);

/*
http://localhost:3000/api/date?timezone=America/New_York 
return : {"localeDateString":"2018-5-5 16:07:41"}
*/
apiRoutes.get('/date', function (req, res) {
    let options = {
        hourCycle: 'h24',
        year: 'numeric',
        month: 'numeric',
        day: 'numeric',
        hour: 'numeric',
        minute: 'numeric',
        second: 'numeric',
        timeZone: req.query.timezone
    };
    let result;

    try {
        let today = new Date();
        result = today.toLocaleDateString([], options);
    } catch (e) {
        result = e.message;
    }

    res.status(200).json({
        'localeDateString': result
    });
    return;
});

app.listen(3000, function () {
    console.log('Node Backend started on port [%d]', 3000);
});

Then, without restarting the backend, if I type http://localhost:3000/api/date?timezone=europe/paris in the browser.
You should see the following result {"localeDateString":"2018-5-5 22:19:28"}.

Note

So we have

  • a webpack-dev-server listening on port 8080 which renders a React App
  • a backend server listening on port 3000 which serves an API

both with automatic reload

What’s next ?

At this point I have 2 autonomous modules without interaction between each other.
Then now, I want my React App requests my Backend API.

Adding interaction between React App and Node Backend

GET request

I update the App.jsx file to add a GET request to the Backend and to display a new Clock in the display area with the response content.

import React from "react";
import Clock from './Clock';

import './App.css';

export default class App extends React.Component {

  constructor(props) {
    super(props);

    this.state = { 'data': {} };
  }

  getRemoteTime(timeZone) {
    let data = {};
    let xhr = new XMLHttpRequest();

    xhr.open('GET', encodeURI('/api/date?timezone='.concat(timeZone)));
    xhr.responseType = 'json';
    xhr.onload = () => {
      if (xhr.status === 200) { data = xhr.response; }
      this.setState({ 'data': data });
    };

    xhr.send();
  }

  componentDidMount() {
    this.timer = setInterval(() => this.getRemoteTime("America/New_York"), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {

    return (
      <div className="container">
        <div className="header item">
          <Clock />
          <Clock><b>Lyon</b></Clock>
          <Clock time={new Date().toLocaleDateString([],
            {
              hourCycle: 'h24', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZone: "Europe/Paris"
            })}><i>Fixed/Init_Time</i></Clock>
          <Clock timeZone="Europe/London" />
          <Clock timeZone="Europe/Berlin" />
          <Clock timeZone="America/New_York" />
          <Clock timeZone="Asia/Tokyo" />
          <Clock timeZone="Asia/Unknown_City" />
        </div>
        <div className="content">
          <div className="nav item">React App</div>
          <div className="display item">
            Display area
            <Clock time={this.state.data.localeDateString} />
          </div>
        </div>
        <div className="footer item">&copy; Ent Wickler</div>
      </div>
    );
  }

}

What happens in the browser ?

Yes! There is a new Clock displayed, but wait…
No! It displays the Paris time while I asked for New_York time…

Ok, I open my browser dev tool and in the network panel I can see the following errors (404 not found error)

Of course the React App requests the Api on its own base URL which is http://localhost:8080 but the backend listens on port 3000.

Architecture

graph TD A{Browser}-->|http://localhost:8080| B[webpack-dev-server: 8080] A -->|http://localhost:8080/api| C((fa:fa-question)) C-. unknown .-> B B -.->|auto reload| B style A fill:#5858FA style B fill:#2EFE64 style C fill:#FF0000

But what can I do ?

Bad solution

Just for the try I can hardcode the full backend URL like the following

import React from "react";
import Clock from './Clock';

import './App.css';

export default class App extends React.Component {

  constructor(props) {
    super(props);

    this.state = { 'data': {} };
  }

  getRemoteTime(timeZone) {
    let data = {};
    let xhr = new XMLHttpRequest();

    xhr.open('GET', encodeURI('http://localhost:3000/api/date?timezone='.concat(timeZone)));
    xhr.responseType = 'json';
    xhr.onload = () => {
      if (xhr.status === 200) { data = xhr.response; }
      this.setState({ 'data': data });
    };

    xhr.send();
  }

  componentDidMount() {
    this.timer = setInterval(() => this.getRemoteTime("America/New_York"), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {

    return (
      <div className="container">
        <div className="header item">
          <Clock />
          <Clock><b>Lyon</b></Clock>
          <Clock time={new Date().toLocaleDateString([],
            {
              hourCycle: 'h24', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZone: "Europe/Paris"
            })}><i>Fixed/Init_Time</i></Clock>
          <Clock timeZone="Europe/London" />
          <Clock timeZone="Europe/Berlin" />
          <Clock timeZone="America/New_York" />
          <Clock timeZone="Asia/Tokyo" />
          <Clock timeZone="Asia/Unknown_City" />
        </div>
        <div className="content">
          <div className="nav item">React App</div>
          <div className="display item">
            Display area
            <Clock time={this.state.data.localeDateString} />
          </div>
        </div>
        <div className="footer item">&copy; Ent Wickler</div>
      </div>
    );
  }

}

But it’s the same result even if the error is different

It is the well known security problem of XSS.

Architecture

graph TD A{Browser}-->|http://localhost:8080| B[webpack-dev-server: 8080] A -->|http://localhost:3000/api| C((fa:fa-question)) C-. forbidden .-> D[fa:fa-ban nodedemon backend: 3000] B -.->|auto reload| B D -.->|auto reload| D style A fill:#5858FA style B fill:#2EFE64 style C fill:#FF0000 style D fill:#FF0000

Solving the problem

First I go back to the original code with the relative URL

import React from "react";
import Clock from './Clock';

import './App.css';

export default class App extends React.Component {

  constructor(props) {
    super(props);

    this.state = { 'data': {} };
  }

  getRemoteTime(timeZone) {
    let data = {};
    let xhr = new XMLHttpRequest();

    xhr.open('GET', encodeURI('/api/date?timezone='.concat(timeZone)));
    xhr.responseType = 'json';
    xhr.onload = () => {
      if (xhr.status === 200) { data = xhr.response; }
      this.setState({ 'data': data });
    };

    xhr.send();
  }

  componentDidMount() {
    this.timer = setInterval(() => this.getRemoteTime("America/New_York"), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {

    return (
      <div className="container">
        <div className="header item">
          <Clock />
          <Clock><b>Lyon</b></Clock>
          <Clock time={new Date().toLocaleDateString([],
            {
              hourCycle: 'h24', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZone: "Europe/Paris"
            })}><i>Fixed/Init_Time</i></Clock>
          <Clock timeZone="Europe/London" />
          <Clock timeZone="Europe/Berlin" />
          <Clock timeZone="America/New_York" />
          <Clock timeZone="Asia/Tokyo" />
          <Clock timeZone="Asia/Unknown_City" />
        </div>
        <div className="content">
          <div className="nav item">React App</div>
          <div className="display item">
            Display area          
            <Clock time={this.state.data.localeDateString}>Remote time for {this.state.data.timeZone}</Clock>
          </div>
        </div>
        <div className="footer item">&copy; Ent Wickler</div>
      </div>
    );
  }

}
No CORS package

I am going to set a proxy on port 8080 to access the backend on port 3000

var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',

  entry: [
    path.resolve(__dirname, "./index.jsx")
  ],

  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "bundle.js"
  },

  plugins: [
    new HtmlWebpackPlugin({
      title: 'React App with Node Backend',
      filename: 'index.html',
      template: 'index.htm'
    })
  ],

  devServer: {
    proxy: {
      "/api": "http://localhost:3000"
    }
  },

  module: {

    rules: [
      /* both JS and JSX files are processed so I could name a JSX file with ".js" suffix*/
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env', 'react']
          }
        }
      },

      /* css */
      {
        test: /\.css$/,
        use: [{
            loader: "style-loader"
          },
          {
            loader: "css-loader"
          }
        ]
      }

    ]

  },

  /* our './myComponent' requires or imports are equivalent to 'myComponent.jsx' */
  resolve: {
    extensions: ['*', '.js', '.jsx']
  }
}
I restart the webpack-dev-server and it is all right.

  • In the browser I can see now

  • In the dev tool, no more network error too

Architecture

graph TD A{Browser}-->|http://localhost:8080| B[webpack-dev-server: 8080] A -.->|http://localhost:8080/api| B B -.->|http://localhost:3000/api| C[nodedemon backend: 3000] B -.->|auto reload| B C -.->|auto reload| C style A fill:#5858FA style B fill:#2EFE64 style C fill:#2EFE64

Conclusion

We have implemented a development environment with automatic update for a React / Backend architecture.

Note that if you want to render the React App from your Backend you need to modify the devServer element of the webpack.config.js file like this

devServer: {
    historyApiFallback: true, /* for react-router if needed */
    proxy: {
      "*": "http://localhost:3000" /* all requests are proxified */
    }
  }

comments powered by Disqus