67

I'm using webpack to run my react frontend successfully using the following config:

{
    name: 'client',
    entry: './scripts/main.js',
    output: {
        path: __dirname,
        filename: 'bundle.js'  
    },
    module: {
        loaders: [
            {
                test: /.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                query:{
                    presets: ['es2015', 'react', 'stage-2']
                }
            }
        ]
    }
}

I'm trying to put up a node.js express backend as well, and would like to run that through webpack as well, so that I have a single server running both the backend and frontend, and because I want to use babel to transpile my javascript.

I made a quick testserver looking like this:

var express = require('express');
console.log('test');

var app = express();

app.get('/', function(req, res){
    res.send("Hello world from Express!!");
});

app.listen(3000, function(){
    console.log('Example app listening on port 3000');
});

If I run this with node index.js and open my browser on localhost:3000 it prints "Hello world from Express!!". So far so good. Then I tried creating a web-pack config for it:

var fs = require('fs');
var nodeModules = {};
fs.readdirSync('node_modules')
    .filter(function(x) {
        return ['.bin'].indexOf(x) === -1;
    })
    .forEach(function(mod) {
        nodeModules[mod] = 'commonjs ' + mod;    
});

module.exports = [
{
    name: 'server',
    target: 'node',
    entry: './index.js',
    output: {
        path: __dirname,
        filename: 'bundle.js'
    },
    externals: nodeModules,
    module: {
        loaders: [
            { 
                test: /\.js$/,
                loaders: [
                    'babel-loader'
                ]
            },
            {
                test:  /\.json$/, 
                loader: 'json-loader'
            }
        ]
    }
}   

When I run the command webpack-dev-server it starts up successfully (it seems). However, if I go to my browser on localhost:3000 now, it just says that the webpage is not available, just as when the server is not running at all.

I'm very new to both node and webpack, so either I have made a small mistake somewhere, or I'm way off ;)

Øyvind Bråthen
  • 54,098
  • 26
  • 117
  • 143

5 Answers5

99

Webpack-dev-server is great for client side development but it will not deploy Express api's or middleware. So in development I recommend running two separate servers: One for the client and one for your server side api's.

Nodemon npm install --save-dev nodemon is a good backend development server that will give you hot-redeploy of your api's, or you can just use express and restart when you make changes. In production the client and api will still be served by the same express server.

Set a lifecycle event for both nodemon and webpack-dev-server in your package.json to make starting them easy (example: npm run dev-server).

"scripts": {
   "start": "webpack --progress --colors",
   "dev-server": "nodemon ./server.js localhost 8080",
   "dev-client": "webpack-dev-server --port 3000",
}

Or, to run express directly from node:

"scripts": {
   "start": "webpack --progress --colors",
   "dev-server": "node dev-server.js",
   "dev-client": "webpack-dev-server --port 3000",
}
// dev-server.js
const express = require('express');
const app = express();
// Import routes
require('./_routes')(app);   // <-- or whatever you do to include your API endpoints and middleware
app.set('port', 8080);
app.listen(app.get('port'), function() {
    console.log('Node App Started');
});

Note: The api server must use a different port than webpack-dev-server.

And finally in your webpack-dev-config you need to use a proxy to redirect calls to your api to the new port:

devServer: {
  historyApiFallback: true,
  hot: true,
  inline: true,

  host: 'localhost', // Defaults to `localhost`
  port: 3000, // Defaults to 8080
  proxy: {
    '^/api/*': {
      target: 'http://localhost:8080/api/',
      secure: false
    }
  }
},
// and separately, in your plugins section
plugins: [
  new webpack.HotModuleReplacementPlugin({
    multiStep: true
  })
]

**Bonus points for having a single script to start and kill both

Cristian Muscalu
  • 6,593
  • 10
  • 32
  • 67
perilandmishap
  • 2,453
  • 2
  • 20
  • 27
  • 1
    Thank you @perilandmishap. Question: when we are about to go to production, do we put all of the client side code with the server side? – Sequential Feb 24 '17 at 23:17
  • 1
    @Sequential Not sure I understand the question. You should use the same file structure for production and dev, it's a good general rule to keep your dev environment as close as possible / convenient to production. Express will serve both API and client. webpack-dev-server will not, but we want to use it for development because hot reload is awesome. – perilandmishap Mar 09 '17 at 17:13
  • Honestly, I don't use express for my production api's (we're old school and use Java w/ Jetty). I just have on two api calls to express to get the url for the java api so we can store it in an environment variable in our cloud thing. We use heroku pipelines and this is the secret sauce that makes them go with a separate api server. – perilandmishap Mar 09 '17 at 17:15
  • 5
    This setup does not address ay CORS issues. Making requests from one port to another is cross domain access, and if not handled will throw an error. (Check with chrome tools) –  Jun 07 '17 at 07:04
  • Beautiful! Thanks! – dwjohnston Jul 19 '17 at 22:14
  • 4
    @stevematdavies actually, since it uses a proxy, CORS shouldn't really be an issue, right? – Adam Gerthel Sep 06 '17 at 11:47
  • I haven't had an issue with CORS on this setup, but I do have one with browsersync – ness-EE Oct 25 '17 at 09:48
  • I can't say how long I tried getting both the frontend and backend running through node for it to fail again and again. This definitely seems like a better way during dev to go about things, thank you! – Jack Mar 17 '18 at 13:26
  • It doesn't help you with hydration errors, which is what I'm currently searching an answer for. – html_programmer May 20 '19 at 17:40
25

Since webpack-dev-server is just a tiny express server with compile on change and hot reload.

So, if you already got a express server for backend API, just merge the compile on change and hot reload into your express server.

Then after take a look at the package.json of webpack-dev-server, i find the key is just webpack-dev-middleware

const express = require('express'); //your original BE server
const app = express();

const webpack = require('webpack');
const middleware = require('webpack-dev-middleware'); //webpack hot reloading middleware
const compiler = webpack({ .. webpack options .. }); //move your `devServer` config from `webpack.config.js`


app.use(middleware(compiler, {
  // webpack-dev-middleware options
}));

app.listen(3000, () => console.log('Example app listening on port 3000!'))

So, when you run your BE server, it will compile all the things using webpack, and watch for changes, LOL ~

Also, add webpack-hot-middleware for hot reloading function, see Hot Module Replacement

Shawn Wang
  • 1,231
  • 12
  • 18
  • Tested and works well. If you use plugins, those need to be added to package as well and manually required. Requires some digging around for API usage. – Eddie Dec 18 '18 at 02:12
  • It works, except reload (not hot reload) didn't worked out of the box. – soeik Dec 18 '18 at 15:50
  • 3
    Upvoted, I actually prefer this answer to the top-voted one since it consolidates my backend and frontend in a cleaner way. My implementation was slightly different but similar in spirit. I have server.js do `module.exports = app` and a wrapper `dev-start.js` script that adds webpack middleware to the returned object similar to the code above. That way I spawn server.js in production directly and this webpack logic is not even loaded unless I'm in dev. – Alexander Tsepkov Apr 28 '19 at 20:39
8

From your questions here and here, it appears that you are using ReactJS with ES6. I faced the exact same issue, and here is how I tackled it -

  1. Have multiple entry points for your application

In particular you can put all your vendor files like JQuery, React etc into one chunk. This way, your vendor files will remain the same even when you modify your souce files. You can add this line to your webpack config

entry: {
    vendors: ['react','reactDom','jquery'] //Any other libraries
}

Use the CommonsChunkPlugin to have webpack determine what code/modules you use the most, and put it in a separate bundle to be used anywhere in your application.

plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendors', 'dist/js/vendors.js', Infinity),
]
  1. Use React Hot Loader

Run npm install react-hot-loader --save-dev. Make sure you have installed webpack-dev-server first.

Then you need to change your loaders to this -

loaders: [
        { 
            test: /\.jsx?$/, 
            loaders: ['react-hot'],
            include: path.join(__dirname, 'public')

        },{ 
           loader: 'babel',
            query: {
                presets: ['react', 'es2015']
            },
            include: path.join(__dirname, 'public')
        }, 
    ]

Make sure React Hot Loader comes before Babel in the loaders array. Also make sure you have include: path.join(__dirname, 'public') to avoid processing node_modules, or you may get an error like this -

Uncaught TypeError: Cannot read property 'NODE_ENV' of undefined

  1. Modifications to your script tags in your index.html page

If your html has something like this -

<script src="/dist/js/vendors.js"></script>
<script src="/dist/js/app.bundle.js"></script>

Change this to point to your webpack-dev-server proxy -

<script src="http://localhost:8080/dist/js/vendors.js"></script>
<script src="http://localhost:8080/dist/js/app.bundle.js"></script>
  1. Run webpack-dev-server --hot --inline,

wait for the bundling to finish, then hit http://localhost:3000 (your express server port) in your browser.

If you run into any errors, you could find this troubleshooting guide very useful.

Hope this helps, and you can take a look at the webpack setup for my project here

Community
  • 1
  • 1
booleanhunter
  • 5,470
  • 4
  • 13
  • 20
  • 6
    Maybe I'm mistaken, but isn't this setup for a frontend application? What if you only want to serve the backend (express) app? – Spock May 11 '16 at 07:14
  • Correct me if I'm wrong, but are you asking how to use webpack with NodeJS/Express code (nothing related to client at all)? – booleanhunter May 12 '16 at 11:42
  • That is my issue.. I have a repo with just the node backend.. I managed to get it running, but without using webpack-dev-server, I just used nodemon --watch instead. – Spock May 13 '16 at 12:02
  • Okay, so you don't need to use webpack for that. Webpack is for resolving client-side stuff like module bundling, minifications etc. – booleanhunter May 16 '16 at 06:34
  • 6
    > oh but I do want to use Webpack for the backend. Things like typescript support, bundling, minification, resource loaders etc etc are brilliant to use with Webpack - also on the backend. In the end I used nodemon to run the Webpack generated bundles.. it works flawlessly.. – Spock May 16 '16 at 12:49
1

Just faced the same issue and came with another solution (found out more information about it later, but here it is).

Instead of using the webpack-dev-server, use the webpack --watch command so files are compiled again upon changes. Once the files are updated on the dist (or any other compiled files folder) you can set to run the nodemon on the dist folder and watch only the dist files.

This way it is possible to have the express server running and serving the front-end as you would in a production environment (or kinda) and benefit from the fast reloads.

Here's a link with some solutions to combine the webpack watch and nodemon.

My scripts section is something like this at this moment (I'm using the run-all solution):

  "scripts": {
    "serve": "npm-run-all --parallel serve:webpack serve:nodemon",
    "serve:webpack": "webpack --progress --colors --watch",
    "serve:nodemon": "nodemon ./dist/app.js --watch dist"
  },
WilDrow
  • 41
  • 3
0

I found this to be a really simple solution that works with create-react-app, where you just want to use npm start to start the webpack-dev-server and you can't mess around with the webpack config. Just use http-proxy-middleware in Express to proxy all requests that the server doesn't itself handle to the webpack-dev-server:

import express from "express"
import { createProxyMiddleware } from "http-proxy-middleware"
const app = express()

// Put your web APIs here, for example:
app.get("/hello", (req, res) => {
   res.send("Hello World")
})

...

// This goes after all your other routes:
if (!isProduction) {
   app.use("*", createProxyMiddleware({ target: "http://127.0.0.1:3000", ws: true }))
}

app.listen(5000)

Note 1: To keep this simple, I am not using HTTPS. (Use environment variable HTTPS=false to have webpack-dev-server use HTTP.)

Note 2: You only want to create the proxy in development mode - in production, you would probably use express.static to serve your compiled single-page app.

Run npm start on your React project and start your Express server. Then (using the port numbers in the example code) browse to http://localhost:5000. You will see your React front-end and it will be able to send API requests to your Express server, all on port 5000. Hot module replacement works too!

Rand Scullard
  • 2,325
  • 1
  • 19
  • 17