DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Kedar
Kedar

Posted on • Updated on

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

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);
});
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ†• 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"]
}
Enter fullscreen mode Exit fullscreen mode

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"
        }
      }
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

โญ๏ธ 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>
Enter fullscreen mode Exit fullscreen mode

โœ๏ธ 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'));
Enter fullscreen mode Exit fullscreen mode

โšก๏ธ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"
        }
      }
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ‰ 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;
}
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

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']
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ‰ 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'));
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ง 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]' }
}
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

๐Ÿ™…๐Ÿผโ€โ™‚๏ธ 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
Enter fullscreen mode Exit fullscreen mode

๐Ÿบ 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!

Top comments (16)

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 • Edited on

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
treuh profile image
ะั€ั‚ั‘ะผ ะŸะพะณัƒั€ัะบะธะน

Thank you so much!!!

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
bvsbrk profile image
bharath

Wow this is a great article. It gave a good intro to everything I needed in one single article. Thanks a lot

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
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
handy117 profile image
Ronny

Wow!! Amazing. :)
It works well.

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 • Edited on

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.

Collapse
barocsi profile image
barocsi

So much tooling for so little business value.

๐ŸŒš Friends don't let friends browse without dark mode.

Sorry, it's true.