Do you find yourself having to run two web servers to power your React website - one for the front-end built with create-react-app and one for the back-end powered by Node.js and Express? Do you sometimes experience complications and downtime due to your two-server setup?
In this article, we explain how to:
- Create a React app from scratch instead of using create-react-app
- Streamline your setup to run your full-stack React website on a single web server port
- Use Babel and Webpack to allow the browser to access and read your React code
Create your project directories
To get started, create a project folder and change into the new directory. In this example, we will call it full-react-site. Use the following shell commands:
mkdir full-react-site
cd full-react-site
Next, create three folders organize your code:
mkdir public
mkdir src
mkdir server
Now initialize the directory with npm:
npm init -y
Install the prerequisite packages
You will also need to install the necessary packages for React, Express, Webpack and Babel:
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli
npm install express react react-dom
So far we have installed the packages required to load React and JSX, but nothing else. If you would like to use Sass or CSS in your React app, make sure your React code can load them by installing these packages:
npm install --save-dev css-loader sass-loader node-sass
Set up Babel
Now make a new file in your project folder called .babelrc with the following content:
{
"presets": ["@babel/preset-env","@babel/preset-react"]
}
Create your React entry point
Now, before setting up Webpack, you will need to create a React entry point and a very basic HTML page to load your compiled React bundle. Create a file called entry.jsx in your app's src folder and add this content:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'
const Entry = () => {
return (
<App/>
);
};
ReactDOM.render(<Entry/>,document.getElementById("root"));
As you can see, the code is looking for App in the App.js or App.jsx file. Create the file App.jsx in the same directory as your entry.jsx file and add this content:
import React,{ useState } from 'react';
import './style.scss'; // My Sass Stylesheet
const App = () => {
let [title, setTitle] = useState("Full-Stack React Site")
function changeHandler(event){
setTitle(event.target.value);
document.title=title;
}
return (
<div className="react-app">
<div className="title-app">
<h1>{title}</h1>
<div className="title-app__changer">
<label htmlFor="changer">Change the title!</label>
<input type="text" id="changer" value={title} onChange={changeHandler} />
</div>
<p>Hope you are having a nice day.</p>
</div>
</div>
)
}
export default App;
The above is just a simple React app that will change the title of the webpage based on the input entered. Because our entry file entry.jsx imports this App.jsx file, the App.jsx file and its dependencies will be loaded into a bundle by Webpack, ready to be loaded when the App component itself is loaded.
Add a stylesheet
As for the stylesheet, style.scss simply needs to look something like this:
.title-app{
width: 600px;
height: 400px;
padding: 30px;
background: forestgreen;
h1{
color: gold;
font-size: 32px;
}
label, p{
font-size: 18px;
color: lightcyan;
}
&__changer{
input{
font-size: 20px;
border-radius: 5px;
border: 3px dashed darkorchid;
}
}
}
Load the compiled Webpack bundle
We still need something that will load the compiled Webpack bundle. To do this, create a very simple HTML file in the server directory called base.html to load your bundle file. The content of base.html should be something like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Full-Stack React Website</title>
</head>
<body>
<div id="root"></div>
<script defer src="/bundle.js"></script>
</body>
</html>
That simple.
Configure Webpack
To get started configuring Webpack, go to the base of your project directory where package.json and .babelrc are located and create a file called webpack.config.js. This file need to have some basic settings to get your project working, but can also be used to fine-tune your project settings.
Your entry file is src/entry.jsx and your compiled output should be placed into the public folder - let's give it a filename of bundle.js. You also need to ensure that Webpack loads .jsx files with babel-loader, Sass files with sass-loader and CSS files with css-loader.
The following webpack.config.js file does all those things:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/entry.jsx',
output: {
path: path.resolve(__dirname,'public'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\\.(js|jsx)$/,
loader: 'babel-loader'
},
{
test: /\\.(scss|sass)$/,
loader: 'sass-loader'
},
{
test: /\\.(css)$/,
loader: 'css-loader'
}
]
},
resolve: {
extensions: ['jsx','scss','css','js']
}
};
Create a Node-Express server
To create your Node-Express server, create a file called server.js in your server directory. This file will work as a simple Node.js and Express server.
Requests to your API will be answered with a simple JSON message, while all other requests will load base.html. The files in your public directory will be accessible to anyone, so a reference to /bundle.js will load your webpack bundle (public/bundle.js) once it has been compiled.
Here is the server.js code:
const express = require('express');
const path = require('path');
const app = express();
const port = 8000;
app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(express.static(path.resolve(__dirname,'../public')));
// Now for our API route
app.use('/api',(req,res,next)=>{
console.log("Someone went to the API");
res.json({message: "This is the API"});
});
// Because they must have missed every other route, we send the bundle loading file.
app.use((req,res,next)=>{
console.log("Someone used the front-end");
res.sendFile(path.resolve(__dirname,'/base.html'));
})
app.listen(port,()=>{
console.log(`App now listening on port ${port}`);
});
Add build and start commands
Now, to finish, add build and start commands to package.json so that you can use "npm run build" and "npm start" to run the app. The following code will accomplish this for you:
...
"scripts": {
"build": "webpack",
"start": "node ./server/server.js"
},
...
Start your app with API
To build and run your website app with separate API, type the following shell comments from within the root folder of your project:
npm run build
npm start
You should then be able to load your React-driven website and access your API through the following URLs:
React website:
http://localhost:8000
JSON API:
http://localhost:8000/apiFinal comments
If you have followed the above instructions, then hopefully you have built a full-stack MERN-ready project from scratch.
During the above process, Webpack combined and compressed React, React DOM and our React code into public/bundle.js. The React application can then be loaded by any HTML page containing a div with an id set to load the file.
While it's understandable that developers use simple tools like create-react-app, knowing how to build apps from scratch gives you the knowledge you need to create full stack applications.
Learning how these small parts of the system work will make you a better developer because you will be in a stronger position to solve problems reliably. Understanding how the app construction process works also empowers you with the choice of using pre-built solutions versus creating your own solution.