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
- Visual Code Studio :
In global mode
- node –version :
v8.11.1
- npm –version :
5.6.0
- node –version :
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 frontendserver
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
- create a class
App.jsx
which will contain the true application with its very usefull functionnalities - 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>© 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
- the
entry
of my React App, it is my previous entry pointindex.jsx
- the
output
is where Webpack creates the resulting bundle file - the
HtmlWebpackPlugin
allows to define theindex.html
file needed to start the frontend in the browser. It is based on the template fileindex.htm
. - the
rules
of themodules
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">© 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">© 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:
- we do a
npm init -y
to initialize our directory - we install
express
withnpm 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">© 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
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">© 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
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">© 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']
}
}
Architecture
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 */
}
}