Creating a Node Express-Webpack App with Dev and Prod Builds

Looking for a great (remote only) React Dev? Visit my profile on LinkedIn and say hi! 😃

This is a very long article. I’m sorry — it takes time to explain this stuff.

I REALLY struggled with building an error-free Webpack and Express application boilerplate.

Every time I would pass one hurdle, another error would get generated by the next thing in the heap that was breaking. Honestly, it was a very frustrating experience.

Considering the fact that Express and Webpack are amongst the most used technologies on the web, you’d think that there were a lot more blog posts out there about how to set them up together, but no, you’d be wrong.

There are a couple that look at one aspect or another of integration, but besides Alejandro Napoles’ (alejandronapoles.com) FANTASTIC series on the issue, there’s really not much else.

If you like this article or the projects (Expack and Rexpack), please show some love by hitting the Clap/Applause button, starring it on Github, or just tweeting me a quick thank you note at [@bengrunfeld](https://twitter.com/bengrunfeld). I’m not asking this because of some marketing effort — I couldn’t care less about that. The feedback means a lot to me personally, and every single star/clap makes me happy. Thank you!!

Link to code:

Image for post
Image for post

What We Want To Make

Firstly, we want to have both a development build of our code, as well as a production build, because we want to employ certain tools in one but not the other.

For our Development build, we want to transpile it from ES6+, lint it, run unit tests on it, run coverage reports for it, and to enable Hot Module Reloading (HMR) for an easier development experience. Re bundling, we do NOT want to minify it or uglify it, so that we can explore features and find bugs more easily. Similarly, we want the images to stay as their own files so that they are more easily identifiable — as opposed to being encoded in Base64 straight into our CSS file.

For our Production build, we want the file sizes to be as small as possible to increase app loading speed and usage speed (especially on mobile devices, which may have limited bandwidth). We also want there to be as few files as possible to reduce the number of requests from to the server. With all that in mind, we’ll want to minify and uglify our code, with comments and blank space stripped out. We will also want to encode images directly into our CSS files as Base64 to reduce the amount of files (as above).

To avoid unnecessary imports and to keep Dev and Prod concepts clearly seperate, we will have a seperate Express server file for Dev and Prod, and seperate Webpack config files for Dev and Prod application code, and for the server too.

If you’re already confused, please don’t worry. It will become clearer as we build out the app. Once you’ve finished the article, read this section again and it will make more sense.

Our Tech Stack

These are the main technologies we want to employ:

  • Express — server
  • Webpack 4 — bundling
  • Jest — testing
  • Babel — ES6+ transpilation
  • ESlint — Linting
  • Webpack Dev Middleware — Bundle code in memory instead of in a file
  • Webpack Hot Middleware — Enables Hot Module Reloading (HMR)
  • UglifyJS — uglifies code
  • mini-css-extract-plugin — minifies CSS

NOTE: The next article that deals with building a React Express Webpack application will simply add React to this tech stack, with some special configuration changes.

Ok, Let’s Begin…! 🤠

Step 1: The Express Server

I’m running all of this on macOS Sierra 10.12.6, with Node v10.0.0, NPM 6.0.0, Webpack 4, Express 4.16.3.

Let’s start from scratch with a new directory. We will test incrementally as we go, so you can identify errors in a specific area as opposed to installing everything and then at the end having no clue where it is.

First create a package.json.

Now we can install Express.

Add the following to your package.json.

Let’s write a basic Express server file in the project root directory, server.js to test that it works.

And of course, a nice simple HTML file to say “Hello”.

Now, to test that it works, run npm start and navigate to http://localhost:8080. Page should display the HTML correctly.

NOTE: Make sure to open the Console in Browser Dev Tools to ensure that no Javascript or other errors are being generated.

Step 2: Install and Enable Webpack

Let’s install Webpack (currently at version 4) and configure it. We need the CLI tools to call it from the command line, and we need webpack-node-externals to ignore node_modules when creating the build for the server. If we try to build an Express server with node_modules, we get hit with a ton of errors.

And we’ll also install Babel to transpile ES6+ to ES5 (what Browsers still need to consume).

If you’re on Babel 6 and want to upgrade, just run this nifty babel-upgrade tool:

We’re also going to need to install html-loader and html-webpack-plugin to copy our index.html file to the dist directory. This plugin will also insert a script tag in the HTML file that imports the main Javascript file.

Now we must create the Webpack config file — webpack.config.js, and it should look like this:

Note that excludeChunks will exclude a file called server which we don’t want to be included into our HTML file, since that is the webserver, and not needed in the app itself.

Change server.js to have ES6+ import syntax instead of Node’s require to test that Babel transpilation is happening correctly.

Create a file called .babelrc in your root and fill it with this code:

And change package.json scripts to the following:

That way we always start with a fresh dist folder, and we declaratively set development mode from the command line.

Now you can run npm run build and npm start and navigate to http://localhost:8080 to test. At this point, there should be no errors (fingers crossed…). I’m building this step by step as I’m writing this article and the above configuration works for me.

Step 3: Add CSS and Javascript Functionality to App

We have quite a bit of functionality in place already, but lets add CSS styles, Javascript, and images to our app.

To do this, we are going to need to separate our Webpack config into 2 files, which will later even become 3 files. One will only deal with bundling the server code — webpack.server.config.js and the other will deal with bundling the application code — webpack.config.js. Later, we’ll separate this main config file into Dev and Prod versions, and we’ll separate the server file into Dev and Prod versions too.

First, let’s install the necessary dependencies:

Our directory structure should look like this:

Adjust your package.json scripts:

Now update ./src/html/index.html:

Now update ./src/css/style.css:

Now update ./src/index.js to check that imports and styles are working, as well as some basic functionality.

And of course ./src/js/logger.js:

Just move server.js from the root into ./src/server. This keeps the root clean and keeps server code in a location that’s appropriate.

Finally, let’s take care of Webpack config. We’ll start with ./webpack.server.config.js.

And now let’s finish everything off with ./webpack.config.js:

Notice how we use target: ‘web’ for the app builds. This is VERY important, and you’ll get an error if you use target: ‘node’, so be sure to double check it.

If you run npm run build, you should not receive any errors. If you do, hunt them down.

Step 4: Seperate App into Dev and Prod Builds

While the above is good general starting point and will give you most of the things you want for a basic app, we’re going to want to start using different technologies for Dev than for Prod. For example, we’ll want images encoded in Base64 directly into the CSS file in Prod builds, but we’ll want them in regular image file format in Dev builds. Also, we’ll want to enable Hot Module Reloading in Dev only, and definitely not in Prod.

So first, let’s split webpack.config.js into webpack.dev.config.js and webpack.prod.config.js. Also, split ./src/server/server.js into server-dev.js and server-prod.js. They can all have the same code at moment — we’ll change it soon.

So the directory structure will look like this (affected files only):

NOTE: The reasons I split everything out into its own file instead of finding some smart way to have everything in one file is that this is my personal style. I like to be VERY declarative with my projects and my code — some might even say that I’m being overly obvious about what I’m trying to do. This makes it easier to figure out what my initial intentions were later on when I need to return to a part of the project.

Now let’s update the package.json scripts accordingly:

As we said, server-dev.js and server-prod.js will have the same code.

Let’s change webpack.server.config.js to the following.

Here we have used some Webpack 4 goodness that allows the config file to export a function which takes argv as a param. argv has a property mode which tells you what mode flag the webpack command was called with (development or production) from the CLI.

We use argv.mode to decide which server file to use — server-dev.js or server-prod.js.

At this stage, everything should work. Notice how when you run npm run buildDev and then npm start, the source code that appears in the browser (Browser Dev Tools Sources tab — main.js) is very different than if you run npm run buildProd and then npm start. When you use the ` — mode production` flag in Webpack 4, your code gets minified and uglified. Woot!

Ok, let’s apply some build-specific functionality to Dev and Prod so that separating config and server files has a point. Webpack doesn’t have a CSS minifier, so we’re going to install and use our own. This will also encode images into Base64 and place them directly into our CSS file, as we discussed above.

Here is the updated code for webpack.prod.config.js:

webpack.dev.config.js stays the same as before:

To check that all of the above works, use Browser Dev Tools to look at your CSS and you should see the difference — Prod should be minified with a giant-ass image in it, while Dev should have a link to the image file.

Step 5: Add Webpack Dev Middleware

As you’ve noticed so far through iterative development, running npm run buildDev every time you make a change to a file gets REALLY annoying…. fast. Fear not, webpack-dev-middleware comes to the rescue.

Webpack Dev Middleware (WDM) watches your source files and runs a Webpack build anytime you hit save on a file. All changes are handled in memory, so no files are writted to the disk. Hot Module Reloading is build on top of Webpack Dev Middleware, so we need to do this step first anyway.

Let’s install our dev dependencies:

Because WDM has not yet caught up with Webpack 4 as of the time of this writing, it still expects an object to be exported from webpack.dev.config.js, so we can’t export a function that uses argv.mode to set the mode. Instead, we have to hard-code it into the config file.

Let’s update webpack.dev.config.js:

Now we have to update ./src/server/server-dev.js with the WDM code:

Run npm run buildDev and npm start, and that should work. Make a change to a file (e.g. logger.js or index.html) and if you refresh, you should see the change.

Step 6: Add Hot Module Reloading

One final touch before we move on to Linting and Unit Testing is to enable Hot Module Reloading (HRM). Basically, whenever you save a change to a file, Webpack Dev Middleware creates a new build, and HMR executes the change in the browser without you having to refresh the page manually (i.e. Ctrl + R).

Now we need to update webpack.dev.config.js:

And also ./src/server/server-dev.js:

And finally ./src/index.js:

Now just run npm run buildDev and npm start and if you make a change to a JS, CSS, or HTML file and save it, the change will appear almost instantly in the browser without you having to refresh.

Step 7: Add ESLint Code Linting

Create a file in the root called .eslintrc.js with the following:

Then update your webpack.dev.config.js:

And add the following ESlint line disabler to the HMR code in ./src/index.js:

And now if you run npm run buildDev, you should receive 2 errors:

  • Unexpected console statement no-console
  • ’console’ is not defined no-undef

Console statements should not be shipped in production code, which is why the linter is pointing this out. To silence it, you can just add the above comment to disable linting on the offending line in logger.js.

Other ESLint plugins you may need are:

  • eslint-plugin-import
  • eslint-plugin-node
  • eslint-plugin-promise
  • eslint-plugin-react
  • eslint-plugin-standard

Step 8: Add Jest Unit Testing and Coverage

We will use Jest to unit test our code. Jest has way fewer issues than Mocha and Karma when used with Node, Express, and React, so in my opinion, it’s definitely the way to go!

Now create a directory in the root called __mocks__ and inside it two files: fileMock.js and styleMock.js. We’ll use these to mock out large files and styles, since the originals of these aren’t needed for unit testing.

Place the following code in fileMock.js:

Place the following code in styleMock.js:

Now update package.json:

Let’s create an easily testable file. Create a new file ./src/js/adder.js and a test file in a new directory, ./src/js/test/adder.test.js

Here’s the code for adder.js:

And here’s the code for adder.test.js:

Now you can run npm test — the test should pass, and you can also run npm run coverage to get a coverage report.

And there you have it, folks! An Express-Webpack application with Hot Module Reloading, Linting, and Unit Testing with Jest.

Again, if you enjoyed the article or if you found it helpful, please hit the clap button or hit star on Github, or leave a response below! I love those things.

Peace out. =)

Written by

I’m a Front End Engineer who loves React, NextJS, and GraphQL. Need a REMOTE React Developer? Contact me at: https://www.linkedin.com/in/bengrunfeld/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store