Copyright Derek O'Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.
The MERN stack refers to:
React can be used to write the client-side code for a Node.js web application.
Node.js is a server environment that allows us to write JavaScript code on the server-side. Node.js is a minimal environment.
Express is a web framework that is built on top of Node.js. Express is used to write the server-side code - such as routing, database access and session handling - for a Node.js web application. Express is the most common server-side framework for Node.js.
MongoDB is a database management tool that allows us to build server-side databases. We can use MongoDB with Express.
Nodejs uses a program called nvm (node version manager) to control the version of nodejs that is running. To install nvm on a Windows system:
Installing the above two files will result in a version of npm (node package manager) being installed on your computer. Run the command below to see what version of npm has been installed:
nvm list
In order to be able to use npm, you must first run the command below. You should match the version to the one that was listed by running the "nvm list" command above.
nvm use 16.13.0
Several versions of nodejs can be installed on a computer at the same time. If we wish, we can use the command below to install other versions npm:
nvm install 8.15
After we install a version of nodejs, we must run the "nvm use" command before we can use it, as shown below:
nvm use 8.15
Several versions of nodejs can be installed on a computer at the same time. Search the internet and find the number of the newest version of nodejs. Install this version using the "nvm install" command above.
We can list all of the nodejs versions that are installed on a computer using the "nvm list" command, as shown below:
nvm list
We can switch between the active version of nodejs using the "nvm use" command line.
In order to run the server-side code of a Node.js app, we need to install a nodemon. The installation of nodemon can be done at the command prompt, using the command below.
npm install -g nodemon
In each section of these notes, we shall download a .zip file that contains a fully working solution. I recomend that you create a folder called c:\nodejs_projects. Each downloaded .zip file can be placed into a folder of the same name.
Create a folder called c:\nodejs_projects\react_app. Copy the code from this link into the folder.
The project consists of a client and a server folder.
To run the server-side of the project you need to run the command prompt nodemon from inside the project's server folder (ie c:\nodejs_projects\react_app\server).
nodemon
Nodemon will automatically restart the server if any changes are made to the server-side source code.
To run the client-side of the project you need to run the command prompt npm start from inside the project's client folder (ie c:\nodejs_projects\react_app\client).
npm start
IMPORTANT: The server-side command nodemon and client-side command npm start must both be running in two separate command windows for an app to run.
We can replace the client/public/favicon.ico file with our own icon file. We can use a fav icon generator, such as https://realfavicongenerator.net/, to convert an image to an icon. We keep the original filename client/public/favicon.ico and simply overwrite the original icon file with our new icon file.
Use a fav icon generator, such as https://realfavicongenerator.net/, to convert an image to an icon. Overwrite the original icon file with your new icon file.
From the above, we can see that a Node.js application requires both a client-side server and a server-side server. The client-side server is responsible for displaying the React Components. The server-side server is responsible for running the node/Express app on the server. When developing our code, we should consider the client-side and server-side code be two related, but totally separate, systems. Understanding that they are two totally separate systems will make it much easier for you to understand and follow these notes.
The full project code for the "Cars" Worked Example that is described below can be downloaded from this link.
Throughout these "Full Stack Development" notes, we shall develop a "Cars" app. Each section of the notes will contain a link to a .zip file, which contains the fully working code relating to the notes in the given section. Each section of the notes will only highlight the changes that have been made in that section of the notes compared to what had been done in previous sections of the notes.
As we can consider the client-side code and server-side code to be two separate systems, each section in the notes will separately describe the Client-Side and Server-Side code, as is done below.
Within the client folder of the project that you downloaded there is a src folder. This is where the React code will be placed.
The file App.js is the route component of the application. Access to all other client-side react Components is via this file.
The client-side code below is the base for all applications. Depending on the application, we shall need to add additional code and files. The client-side code is shown below:
import React, {Component} from "react"
import "bootstrap/dist/css/bootstrap.css"
import "./css/App.css"
import DisplayAllCars from "./components/DisplayAllCars"
export default class App extends Component
{
render()
{
return (
<DisplayAllCars/>
)
}
}
We need to include any system core Components that we shall use in a particular file.
import React, {Component} from "react"
We import any core CSS that we shall use in a particular file.
import "bootstrap/dist/css/bootstrap.css"
We need to import any user-defined, project-specific, CSS that we shall use in a particular file.
import "./css/App.css"
We need to import any user-defined, project-specific, Components that we shall use in a particular file.
import DisplayAllCars from "./components/DisplayAllCars"
Render the DisplayAllCars Component.
export default class App extends Component { render() { return ( <DisplayAllCars/> ) } }
The three files listed below are generated by the system. They do not need to change between applications:
All of the apps' components should be held in a folder called "client/src/components".
import React, {Component} from "react" import CarTable from "./CarTable" export default class DisplayAllCars extends Component { constructor(props) { super(props) this.state = { cars:[] } } componentDidMount() { const cars = [{_id:0, model:"Avensis", colour:"Red", year:2020, price:30000}, {_id:1, model:"Yaris", colour:"Green", year:2010, price:2000}, {_id:2, model:"Corolla", colour:"Red", year:2019, price:20000}, {_id:3, model:"Avensis", colour:"Silver", year:2018, price:20000}, {_id:4, model:"Camry", colour:"White", year:2020, price:50000}] this.setState({cars: cars}) } render() { return ( <div className="form-container"> <div className="table-container"> <CarTable cars={this.state.cars} /> </div> </div> ) } }
We need to include the line of code below in every user-defined Component
import React, {Component} from "react"
Any other core or user-defined Components that are being used by a given Component must be imported into that Component's class file. In this case, the user-defined Component CarTable is imported.
import CarTable from "./CarTable"
Other than the additional code listed above, this and all of the other user-defined Components are coded in exactly the same way as they would be if we were using React in a stand-alone HTML file.
import React, {Component} from "react" import CarTableRow from "./CarTableRow" export default class CarTable extends Component { render() { return ( <table> <thead> <tr> <th>Model</th> <th>Colour</th> <th>Year</th> <th>Price</th> </tr> </thead> <tbody> {this.props.cars.map((car) => <CarTableRow key={car._id} car={car}/>)} </tbody> </table> ) } }
import React, {Component} from "react" export default class CarTableRow extends Component { render() { return ( <tr> <td>{this.props.car.model}</td> <td>{this.props.car.colour}</td> <td>{this.props.car.year}</td> <td>{this.props.car.price}</td> </tr> ) } }
// This file holds global constants that are visible on the Client-side
There are no client-side global constants in this program. We shall use this in later examples.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Example MERN App"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Example MERN App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
We only need to change the title of the app in this system-generated file.
{ "short_name": "Example MERN App", "name": "Example MERN App", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" }
We only need to change the title of the app in this system-generated file.
{
"name": "jwt",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.19.0",
"bootstrap": "^4.3.1",
"react": "^16.9.0",
"react-bootstrap": "^1.0.0-beta.11",
"react-dom": "^16.9.0",
"react-router-dom": "^5.0.1",
"react-scripts": "3.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"description": "Example MERN App",
"main": "index.js",
"devDependencies": {},
"author": "Derek O Reilly",
"license": "ISC"
}
We only need to change the description of the app and the app's author in this system-generated file.
The server-side code below is the base for all applications. Depending on the application, we shall need to add additional code to this file. The server-side code is shown below:
# This file holds global constants that are visible on the Server-side # Port SERVER_PORT = 4000 # Local Host LOCAL_HOST = http://localhost:3000
The server/config/.env file is hidden from browsers, so it can be used to hold secret keys.
// Server-side global variables require(`dotenv`).config({path:`./config/.env`}) // Express const express = require(`express`) const app = express() app.use(require(`body-parser`).json()) app.use(require(`cors`)({credentials: true, origin: process.env.LOCAL_HOST})) // Port app.listen(process.env.SERVER_PORT, () => { console.log(`Connected to port ` + process.env.SERVER_PORT) }) // Error 404 app.use((req, res, next) => {next(createError(404))}) // Other errors app.use(function (err, req, res, next) { console.error(err.message) if (!err.statusCode) { err.statusCode = 500 } res.status(err.statusCode).send(err.message) })
The code above is the minimum base-code that is needed for all apps. Later in the notes, when we develop more complex examples, we shall add additional code to this file.
In order to access to the environment file variables, we need to import the dotenv package.
dotenv must be installed (using the command line npm install -g dotenv) before we can use it.
We access environment variables using the process.env.variableName. In the code below, we access two environment variables, named LOCAL_HOST and SERVER_PORT. Both of these variables have been declared in the environment file system\config\.env
// Server-side global variables require(`dotenv`).config({path:`./config/.env`}) // Express const express = require(`express`) const app = express() app.use(require(`body-parser`).json()) app.use(require(`cors`)({credentials: true, origin: process.env.LOCAL_HOST})) // Port app.listen(process.env.SERVER_PORT, () => { console.log(`Connected to port ` + process.env.SERVER_PORT) }) // Error 404 app.use((req, res, next) => {next(createError(404))}) // Other errors app.use(function (err, req, res, next) { console.error(err.message) if (!err.statusCode) { err.statusCode = 500 } res.status(err.statusCode).send(err.message) })
In order to use Express, we need to import the Express-related packages. The code below is the minimum required for a basic app. We shall add additional Express-related packages in later examples.
body-parser simplifies incoming requests, as it makes the incoming request body available under req.body property.
cors enables CORS (Cross-Origin Resource Sharing) requests.
// Express const express = require(`express`) const app = express() app.use(require(`body-parser`).json()) app.use(require(`cors`)({credentials: true, origin: process.env.LOCAL_HOST}))
The Port is the URL where the server-side code will run.
// Port app.listen(process.env.SERVER_PORT, () => { console.log(`Connected to port ` + process.env.SERVER_PORT) })
The code below deals with server-side error-handling.
// Error 404 app.use((req, res, next) => {next(createError(404))}) // Other errors app.use(function (err, req, res, next) { console.error(err.message) if (!err.statusCode) { err.statusCode = 500 } res.status(err.statusCode).send(err.message) })
Convert the code from this link into a Node.js app.
Hint 1: Use the code from the React_app above as a starting template
Hint 2: Put the Login component into its own file and replace
class Login extends React.Componentwith
import React, {Component} from "react" export default class Login extends ComponentHint 3: In App.js, load the Login component instead of the DisplayAllCars component.
Convert the code from this link into a Node.js app.
Hint 1: The JSON file needs to be put in a json folder inside the client\public folder
Hint 2: The image file for the modal needs to be put in an images folder inside the client\public folder
Hint 3: The global variable ASCENDING needs to be put inside the client\src\config\global_constants.js, as shown below:
export const ASCENDING = 1Click here for the solution.
Copyright Derek O' Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.