loading...

Creating a Node app with React, Webpack 4, Babel 7, Express and Sass

kedar9 profile image Kedar Updated on ・9 min read

By your powers combined...

🏁 Prologue

🆕 Make a new directory. Let's call it react-boilerplate.
mkdir react-boilerplate

And cd into it.
cd react-boilerplate

Make sure you have node and npm installed. Run these commands to make sure:
node -v
npm -v
If you don't have either of it, click here and install them first.

🎬 Now, initialize the node project.
npm init

✨ You will be prompted to enter some basic information. Once that is entered and done, you should have a package.json file in your folder.

🌲 Chapter 1: Tree of Life

1.1 Express

First things first: we make a server. We're using Express.js framework so that we can architect our server, handle our routes and build RESTful APIs.

If handling routes and APIs is not your requirement, you can still use Express or to keep things simpler, you can look into webpack-dev-server.

📦 Install Express.js:
npm install --save express

✨ A folder called node_modules should automatically be created. Anything we install in our project will reside in that folder.

🆕 Time to write the server. Create a new folder called server. In that new folder, create a file index.js. Add this basic minimal code to that file:

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const mockResponse = {
  foo: 'bar',
  bar: 'foo'
};
app.get('/api', (req, res) => {
  res.send(mockResponse);
});
app.get('/', (req, res) => {
 res.status(200).send('Hello World!');
});
app.listen(port, function () {
 console.log('App listening on port: ' + port);
});

This allows the app to run on the specified port.
The code also tells the app that home route: "/" should return a status of 200 (success) and send the text: Hello World. Basic enough!
It also has a route "/api" that returns a dummy JSON object. It shows how fetching data would work.

⭐️ Remember that the order of the routes matters. When a request comes through, Express starts matching routes from the top. When it matches a route, the response is returned and further routes are not checked.

✏️ Now that the server is set up, in the package.json file, under "scripts", add a start command like so:

"scripts": {
  "start": "node server/index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
}

Here, we're telling Node, that to start the project, start with server/index.js.

🏃🏻‍♂️If you run the npm run start command now, you should get a message:
"App listening on port: 3000" on the terminal.

🎉 Now go to http://localhost:3000 on your browser and the "Hello World" message should be showing up there. Go to http://localhost:3000/api and the dummy JSON should show up.

1.2 Webpack

📦 Time to install 

  • webpack (The bundler)
  • webpack-cli (Command Line Interface to be able to run webpack commands)

npm install --save-dev webpack webpack-cli

In the package.json file, under "scripts", add a build command:

"scripts": {
  "start": "node server/index.js",
  "build": "webpack --mode production",
  "test": "echo \"Error: no test specified\" && exit 1"
}

🆕 Now, create a folder called src. This is where most of our project's source code will exist. In that new folder src, create a file index.js.
Leave the file empty for now.

🏃🏻‍♂️If you run the npm run build command, it will create a dist folder with a bundled main.js file in it. The code in it will be minified for production use.

🛰️ Chapter 2: Twilight Ozone

2.1 Babel

🤗 React embraces JSX. (Although JS would work perfectly fine too).
Babel helps compile JSX syntax to JS.
♻️ Not just that, but for regular .js files, we can now use the ES6 syntax and Babel will compile that to its equivalent ES5 form.

📦 Install

  • @babel/core (To transform ES6+ code to ES5)
  • @babel/preset-env (Preset to allow polyfills)
  • @babel/preset-react (Preset for React and JSX)
  • babel-loader (Webpack helper)

npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader

Among these 4 new packages, 2 of them are presets. Babel core needs to know that it has these plugins. They need to be specified.

🆕 On the project's root level, create a .babelrc file. And specify the presets as an array like so:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

If your code needs to be polyfilled, you will also need these Node packages: core-js and regenerator-runtime. More about that here.

2.2 Babel + Webpack

Based on Babel's functionality, Webpack needs to know that .js and .jsx files need to go through Babel before being bundled.
So, we need to configure Webpack for that rule.

🆕 On the project's root level, create a webpack.config.js file. This will be the file for all webpack configs. Add the rule to it like so:

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

⭐️ Remember: Since webpack bundles everything and creates a simple browser-readable code, all packages, presets and plugins you install will need to be configured in webpack.

🏖️ Chapter 3: Utopia

3.1 React

Time to install react and react-dom.
npm install --save react react-dom

🆕 In the folder src/, create a new file index.html. This will be the main and the only HTML file in our project.
It would be like any regular HTML file you've made, with one difference: It needs a <div> in the <body> that React can populate
🔍 And it needs some form of identifier that React can lookup for.
We set id="root" on the div. You can set the id to anything you want.
Here's what a simple index.html with <div id="root"></div> looks like:

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

✏️ Remember that empty src/index.js file we created in Section 1.2?
Time to add code to it:

import React from 'react';
import ReactDOM from 'react-dom';
const Index = () => {
  return <div>Welcome to React!</div>;
};
ReactDOM.render(<Index />, document.getElementById('root'));

⚡️Here, Index is a functional component that returns a React element. And ReactDOM renders it inside the <div id="root"></div> from index.html.

3.2 React + Webpack

Similar to what we did for .js and .jsx files, we need to tell Webpack what to do with the new .html file. Webpack needs to bundle it to the dist folder.

📦 For that we install html-webpack-plugin.
npm install --save-dev html-webpack-plugin

✏️ The webpack config file needs to be updated to handle this plugin. We also tell webpack that the now-coded src/index.js is the entry point.
Here's what the config file looks like after adding that:

const HtmlWebPackPlugin = require("html-webpack-plugin");
const path = require('path');
const htmlPlugin = new HtmlWebPackPlugin({
  template: "./src/index.html", 
  filename: "./index.html"
});
module.exports = {
  entry: "./src/index.js",
  output: { // NEW
    path: path.join(__dirname, 'dist'),
    filename: "[name].js"
  }, // NEW Ends
  plugins: [htmlPlugin],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

When instantiating htmlPlugin, the template option tells webpack what file to pick and the filename option tells what to name the bundled .html file in the dist folder.

3.3 React + Express

🏃🏻‍♂️ If you run npm run start now, we would still get the "Hello World" message on localhost. That is because the Express server does not know of these new files.

✏️ In package.json, the start script simply starts the server.
We now need the webpack to bundle up our files and then start the server. 
Under "scripts", add a new dev command.

"scripts": {
  "start": "node server/index.js",
  "dev": "webpack --mode development && node server/index.js",
  "build": "webpack --mode production",
  "test": "echo \"Error: no test specified\" && exit 1"
}

We should now update Express and change what the route "/" returns.
It should return the dist/index.html file.

✏️ In server/index.js, make the updates (New parts of code end with a comment: // NEW):

const express = require('express');
const path = require('path'); // NEW

const app = express();
const port = process.env.PORT || 3000;
const DIST_DIR = path.join(__dirname, '../dist'); // NEW
const HTML_FILE = path.join(DIST_DIR, 'index.html'); // NEW
const mockResponse = {
  foo: 'bar',
  bar: 'foo'
};
app.use(express.static(DIST_DIR)); // NEW
app.get('/api', (req, res) => {
  res.send(mockResponse);
});
app.get('/', (req, res) => {
 res.sendFile(HTML_FILE); // EDIT
});
app.listen(port, function () {
 console.log('App listening on port: ' + port);
});

🎉 Now run npm run dev and go to http://localhost:3000 on your browser. The "Welcome to React!" message from src/index.js should be showing up there. The "/api" route still works as before.

🍵 Chapter 4: Bottom Line Green

4.1 Sass

Time to make stuff look good. Time to install node-sass and the required loaders: style-loader, css-loader and sass-loader.

npm install --save-dev node-sass style-loader css-loader sass-loader

🆕 Create a new file styles.scss in the folder src/. Add some styles to that file.

Here's a basic (and popular) code to use system fonts on your page.
We also set its color property.

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
  Helvetica, Roboto, Arial, sans-serif;
  color: brown;
}

This would be a good file to add top-level and/or global styles.

✏️ Import the new styles.scss file. You can add it either to the index.html or to the index.js file. To plan for consistency, we import it in index.js file:

import React from 'react';
import ReactDOM from 'react-dom';
import './styles.scss';
const Index = () => {
  return <div>Welcome to React!</div>;
};
ReactDOM.render(<Index />, document.getElementById('app'));

4.2 Sass + Webpack

Like everything else, Webpack needs to know what to do with .scss files to correctly bundle them to a browser-understandable code.

✏️ In webpack.config.js, add the new rule just like the rule we added for babel-loader. So, in the rules array in the module object in module.exports, add:

{
  test: /\.s?css$/,
  use: ['style-loader', 'css-loader', 'sass-loader']
}

🎉 Now run npm run dev and go to http://localhost:3000 on your browser. The "Welcome to React! message" should show up in the system font in brown.

⌛ Epilogue

🖇️ React Components

React projects are made up of a number of smaller components. The Index in src/index.js is one such component. You will be creating more such components and importing them (into one another 🤨).

📂 I would suggest creating a folder called components/ inside the src/ folder. And store all the other components in that folder.

Ideally, inside of components/, create a sub-folder for every component. 
But it's up to the individual preference!

💡 Don't forget that: React component files should export the component Class or function.
Once you add some components to the src/index.js, it would look something like this:

import React from 'react';
import ReactDOM from 'react-dom';
import Header from './components/Header/index.jsx';
import Content from './components/Content/index.jsx';
const Index = () => {
  return (
    <div className="container">
      <Header />
      <Content />
    </div>
  );
};
ReactDOM.render(<Index />, document.getElementById('app'));

🔧 Additional Webpack Configuration

Like other files, images or any other static files also need to be bundled. Webpack needs to know of that.
📦 Install file-loader as a devDependency (--save-dev) for all of such files.
And add the following rule in webpack.config.js:

{
  test: /\.(png|svg|jpg|gif)$/,
  loader: "file-loader",
  options: { name: '/static/[name].[ext]' }
}

In the above code, the test regex only specifies general image extensions. But, you can add any extensions for other files too (in the same rule object).

✏️ To use an image or any other assets in the components, it needs to be imported in that.js/.jsx file first. So, Webpack can bundle it right and make it available in the bundled folder. You could use the actual [name] of the file or [hash] it up. With or without the file [ext].

// Import
import LOGO from '<path-to-file>/logo.png';

...

// Usage
<img src={LOGO} alt="Page Logo" />

🙅🏼‍♂️ Git, ignore!

For deployment, a Node-compatible platform like Heroku or Netlify, runs the build command in your app. That installs all the node_modules and generates the dist folder and its contents.
So, we don't need to push the local folders: node_modules and dist to remote.

🆕 To let Git know this, create a new file .gitignore on the project root level.
Anything you want Git to ignore can be added here. Here's a basic version:

# Deployment directories
node_modules
dist
# Optional npm cache directory
.npm
# Mac
.DS_Store

🍺 That concludes the setup. This project can act as a great boilerplate for any future React w/ server apps or even for stand-alone Express projects.

👍🏼 Thanks for making it all the way through the long article. Setting up an error-free Node app with Webpack and Babel and Express is definitely not a cakewalk. But I hope this article helped you. 

🌏 Go Planet!

Discussion

pic
Editor guide
Collapse
danielledlfs profile image
danielle-dlfs

Big thanks to you and this tutorial :) really helped me!

Just saying, I had an small issue doing 3.3 React + Express.
I don't know if it's because of my node/express/react.. version but in the server/index.js file, I had to add const path = require('path'); so that the path is defined.

And everything is working now :D

Here are my (dev)Dependencies:

"dependencies": {
    "express": "^4.17.1",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.0.0",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.35.0",
    "webpack-cli": "^3.3.4"
  }

and the node version : v12.4.0

Collapse
kedar9 profile image
Kedar Author

I am very glad that this article helped you. :)
You are so right. I dont know how I missed that. I do have the path defined in my project code. Not sure how I did not copy it over.
I will edit the article to include it.
Thanks again :)

Collapse
snorlite profile image
Andrea Betti

Great article!

What should I edit if I want to associate other URLs to other .js files? For example, localhost:3000/test would load a test.js component...

Collapse
kedar9 profile image
Kedar Author

Thanks Andrea.

If you want to do exactly that (i.e. pointing /test to the test.js file),
in the file server/index.js, add:

app.get('/test', (req, res) => {
  res.sendFile('path-to-the-file');
});

Make sure the Express JS version you are using (in package.json), is 4.8.0 or higher. Or else, sendFile wont be supported.

If you need that because you need the .js files for your code and dont need to expose it publically, I would recommend to let webpack take care of it.

Collapse
snorlite profile image
Andrea Betti

I'm sorry, maybe I wasn't clear: what I would like to do is render the content of another component (for example a component Test, located in test.js, and indipendent from Index) inside the div in index.html (or if possible, another html file called test.html).

Writing what you said in your reply, the result is the whole code inside test.js.

Collapse
barocsi profile image
barocsi

So much tooling for so little business value.

Collapse
treuh profile image
Collapse
darkraken007 profile image
Zeeshan Zakariya

You might want to move express.static statement below all the routing calls.

in express the order matters. app.get calls wont get called if the static call serves the get request

Collapse
didin1453fatih profile image
Didin 👨‍💻

I think you can combine Express JS and dbdesigner.id as your database designer. This can make your project readable and clear documentation.

Collapse
handy117 profile image
Ronny

Wow!! Amazing. :)
It works well.

Collapse
randalvance profile image
Randal Vance Cunanan

Thank you, I was looking for this kind of tutorial last week but using Vue instead.

Collapse
kedar9 profile image
Kedar Author

You're welcome.
Hope it helps.

Collapse
anupammaurya profile image
Anupam Maurya

well, explanatory article, good covering all parts especially the src/components and every index.js one!

Collapse
ckcnair profile image
ckcnair

What a great way of explaining things! keep it up.
can we use it with react-router? any configuration changes needed?

Collapse
kedar9 profile image
Kedar Author

Thanks ckcnair.
react-router is a package that provides core routing functionality. Of course, you can use it. Like many other packages.
Configurations will be needed. It depends on the way you'd like to use it.