WittCode💻

Connect a Custom React App to Express

By

Learn to connect a custom built React application to an Express server, how to serve up a custom React application with Express, and how to build a React app from scratch with Webpack.

Table of Contents 📖

Combine Webpack and Express

Combining Express and React is great as we can use the Express server to serve up the React app and also handle any API requests the React app sends. We can make all this possible by using the module bundler Webpack. In fact, combining Webpack and Express provides live reloading and hot module replacement when developing an application, features that are highly desired in popular React frameworks.

Why Not use Webpack Dev Server?

Webpack has an npm package called webpack-dev-server that provides both hot module replacement and live reloading, but under the hood this server is simply an Express server. Furthermore, using the Webpack Dev Server often requires an Express server as well, with the Webpack Dev Server serving the static content while other requests are proxied to Express. A simpler scenario is to have one Express server do it all.

Project Setup

To begin, lets create a project called connect-a-custom-react-app-to-express and initialize it as an ES6 npm project with npm init es6 -y.

mkdir connect-a-custom-react-app-to-express
cd connect-a-custom-react-app-to-express
npm init es6 -y

Now lets install webpack and webpack-cli as development dependencies from npm.

npm i webpack webpack-cli -D

Webpack will allow us to turn our React code into JavaScript code that the browser understands. To do this, we need to install some Babel libraries.

npm i babel-loader @babel/preset-env @babel/preset-react -D

A loader is a function that Webpack uses to convert code from one form to another. Webpack will use the Babel loader will to convert JSX code to code that the browser understands. We also need to install a plugin for Webpack called the html-webpack-plugin.

npm i html-webpack-plugin -D

The html-webpack-plugin will apply our bundled React code to an HTML file. Now lets create our Webpack configuration file.

touch webpack.config.cjs

Now lets fill in our Webpack configuration file.

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

module.exports = {
  mode: 'development',
  target: 'web',
  entry: './src/client/index.jsx',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/',
    filename: 'client.js',
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/client/index.html',
    })
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env',
              ['@babel/preset-react', {'runtime': 'automatic'}]]
          }
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx']
  }
};
  • mode - mode to bundle the application in
  • entry - entry point to start the bundling process
  • target - the environment we will be working in
  • filename - the name of the output bundle code
  • path - directory to output the bundle to
  • publicPath - tells Webpack where to output its bundled files to
  • plugins - taps into the Webpack lifecycle

The rules key accepts an array of rule objects. A rule object tells webpack how to handle a certain module. The rule object in this instance tells Webpack to pass any .js or .jsx files (excluding those inside node_modules) through the Babel loader.

Create a React App

Now lets create our React application. First, lets install the necessary libraries: react and react-dom.

npm i react react-dom

Next, lets create a src directory to hold our source code for the React application.

[object Object]

First lets fill in our index.html file to house the React application.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WittCode</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

The most important part of this is the div element with the id root. That is where our React application will go. Next, lets fill in index.jsx to be the glue between our DOM and React application.

import React from 'react';
import {createRoot} from 'react-dom/client';
import App from './components/App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

This code simply renders our React application in the div element with the id root. Now, lets fill in our App.jsx component.

import {useEffect, useState} from 'react';

const App = () => {
  const [cheeses, setCheeses] = useState([]);
  useEffect(() => {
    fetch('http://localhost:1234/cheese')
      .then(resp => resp.json())
      .then(data => {
        setCheeses(data);
      });
  }, []);

  return cheeses.map((cheese, i) =>
    (<div key={i}>
      <h3>{cheese.name}</h3>
      <h6>{cheese.description}</h6>
    </div>)
  );
};

export default App;

The App component makes an API call to our Express server to retrieve a list of cheeses. We will create this API on our Express server which we will have running on localhost:1234. Remember that this will be the same Express server that serves up our React application.

Create an Express App

Now lets start working with our Express application. To begin, lets install Express from npm.

npm i express

Now lets create a file at the top level of our src folder called server.js to hold our Express application.

touch server.js

Now lets import Express, create our cheese API, and get the application listening for requests on localhost port 1234.

import express from 'express';

const app = express();
const PORT = 1234;

app.get('/cheese', (req, res) => {
  return res.status(200).json([
    {name: 'Cheddar', description: 'Very tasty, I like it.'},
    {name: 'Mozzarella', description: 'Very tasty, Very good OMG'},
    {name: 'Fetta', description: 'Ehhh not the best, but still good'},
    {name: 'Gorgonzola', description: 'Is this how you spell it?'},
  ]);
});

app.listen(PORT, () => {
    console.log(`Server started listening on portsss: ${PORT}`);
});

Now, to get Webpack and Express to work together we need to make use of the npm package webpack-dev-middleware. We can install it from npm as a development dependency.

npm i webpack-dev-middleware -D

The webpack-dev-middleware package emits files processed by webpack to a server. For us, this middleware will emit our bundled React application to this Express server. Lets import this middleware into our server.js file along with the webpack library itself and our Webpack configuration.

import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackConfig from '../webpack.config.cjs';

As webpack-dev-middleware is an Express middleware, it can be mounted in an Express server. Specifically, we bind webpack-dev-middleware as application level middleware by using the app.use function.

app.use(webpackDevMiddleware());

We don't pass a route to app.use in this case as we want it to be called each time a request is sent to the server. The webpackDevMiddleware function takes two arguments. The first is a Webpack compiler. When we import webpack directly we receive a function that returns a webpack compiler.

const compiler = webpack();

We can configure the webpack compiler by passing it a Webpack configuration object. This is the object that we export from webpack.config.cjs.

const compiler = webpack(webpackConfig);

Then we simply need to pass the Webpack compiler to the Webpack middleware function.

app.use(webpackDevMiddleware(compiler, {
  publicPath: webpackConfig.output.publicPath
}));

The second argument to the webpack-dev-middleware function is a configuration object. In this configuration we specify the publicPath to be the one in our Webpack configuration. This is to make sure our webpack-dev-middleware can find the outputted bundle. In other words, this value needs to match the one in our Webpack configuration file.

Webpack Hot Module Replacement

Now, to get our application to reload with changes, we need to use hot module replacement (HMR). HMR detects module changes in an actively running application. This makes the development process more efficient as it only updates what has changed and instantly updates the browser with the modifications. We can enable HMR in a custom Express server by using the npm package webpack-hot-middleware. Install it from npm as a development dependency with npm i webpack-hot-middleware -D.

npm i webpack-hot-middleware -D

Now lets import the package into our server.js file.

import webpackHotMiddleware from 'webpack-hot-middleware';

Next, we need to add this middleware to the express server as application level middleware.

app.use(webpackHotMiddleware(compiler, {}));

The webpack-hot-module-middleware returns a function that accepts a webpack compiler as the first argument and options as a second argument. Next, inside our Webpack configuration file, we need to add the Webpack HMR plugin to the plugins array.

new webpack.HotModuleReplacementPlugin()

The HMR plugin enables Webpack to perform HMR. Finally, we need to alter our Webpack entry point slightly to contain the Webpack hot middleware.

entry: {
  main: ['webpack-hot-middleware/client?reload=true', './src/client/index.jsx']
},

This code configures our Express server to receive a notification when Webpack builds the client bundle. In other words, when our React application is updated, our Express server will be notified of this, causing it to serve up this new bundle. The ?reload=true is an option provided to webpack-hot-middleware that auto-reloads the browser page when Webpack gets stuck.

Running the Application

This is all we have to do to get our Express server working with Webpack. Lets now create a simple script to run this application inside package.json. First, lets install nodemon as a development dependency so we can reload our Express app whenever it changes.

npm i nodemon -D

Now lets create the script to run our Express server.

"scripts": {
  "start": "nodemon ./src/server.js"
},

Now all we need to do is run this script with npm start.

npm start