As is customary, we will start with a Hello World application, something that is a bare minimum and uses most of the MERN components. The main purpose of any Hello World is to show the basic characteristics of the technology or stack that we are using, and the tools required to get it up and running.
In this Hello World application, we’ll use React to render a simple page and use Node.js and Express to serve that page from a web server. This will let you learn the fundamentals of these technologies. This will also give you some basic familiarity with nvm, npm, and JSX transformation—some tools that we’ll use a lot as we go along.
Server-Less Hello World
To quickly get off the ground , let’s write a simple piece of code in a single HTML file that uses React to display a simple page on the browser. No installations, downloads, or server! All you need is a modern browser that can run the code that we write.
Let’s start creating this HTML file and call it index.html. You can use your favorite editor and save this file anywhere on your file system. Let’s start with the basic HTML tags such as <html>, <head>, and <body>. Then, let’s include the React library.
The type can be any HTML tag such as the string 'div', or a React component (which we will start creating in the next chapter). props is an object containing HTML attributes or custom component properties. The last parameter(s) is zero or more children elements, which again are created using the createElement() function itself.
Note
We are using ES2015+ features of JavaScript in this book, and in this snippet, we used the const keyword. This should work in all modern browsers as is. If you are using an older browser such as Internet Explorer 10, you will need to change const to var. Toward the end of the chapter, we will discuss how to support older browsers, but until then, please use one of the modern browsers to test.
A React element (the result of a React.createElement() call) is a JavaScript object that represents what is shown on screen. Since it can be a nested collection of other elements and can depict everything on the entire screen, it is also called the virtual DOM . Note that this is not yet the real DOM, which is in the browser’s memory and that is why it is called a virtual DOM. It resides in the JavaScript engine’s memory as a deeply nested set of React elements, which are also JavaScript objects. A React element contains not just the details of what DOM elements need to be created, but also some additional information about the tree that helps in optimization.
Each of these React elements needs to be transferred to the real DOM for the user interface to be constructed on screen. To do this, a series of document.createElement() calls needs to be made corresponding to each of the React elements. The ReactDOM does this when the ReactDOM.render() function is called. This function takes in as arguments the element that needs to be rendered and the DOM element that it needs to be placed under.
index.html: Server-less Hello World
Exercise: Server-Less Hello World
- 1.
Try to add a class to the h1 element (you will also need to define the class in a <style> section within the <head> section to test whether it works). Hint: Search for “how to specify class in jsx” in stackoverflow.com. Can you explain this?
- 2.
Inspect the element variable in the developer console. What do you see? If you were to call this tree something, what would you call it?
Answers are available at the end of the chapter.
JSX
The simple element that we created in the previous section was easy to write using React.createElement() calls . But imagine writing a deeply nested hierarchy of elements and components: it can get pretty complex. Also, the actual DOM can’t be easily visualized when using the function calls as it can be visualized if it were plain HTML.
Note that although it is strikingly close to HTML syntax, it is not HTML. Note also that the markup is not enclosed within quotes, so it is not a string that can be used as an innerHTML either. It is JSX, and it can be freely mixed with JavaScript.
Now, given all the differences and the complexity in comparison to HTML, why do you need to learn JSX? What value does it add? Why not write the JavaScript itself directly? One of the things I talked about in the Introduction chapter is that MERN has just one language throughout; isn’t this contrary to that?
As we explore React further, you will soon find that the differences between HTML and JSX are not earth-shaking and they are very logical. You will not need to remember a lot or need to look up things as long as you understand and internalize the logic. Though writing JavaScript directly to create the virtual DOM elements is indeed an option, I find that it is quite tedious and doesn’t help me visualize the DOM.
Further, since you probably already know the basic HTML syntax, writing JSX will probably work best. It is easy to understand how the screen will look when you read JSX, because it is very similar to HTML . So, for the rest of the book, we use JSX.
index.html: Changes for Using JSX
Note
Although no effort has been spared to ensure that all code listings are accurate, there may be typos or even corrections that did not make it to the book before it went to press. So, always rely on the GitHub repository ( https://github.com/vasansr/pro-mern-stack-2 ) as the tested and up-to-date source for all code listings, especially if something does not work as expected.
When you test this set of changes, you will find no difference in the appearance of the page, but it could be a wee bit slower due to the compilation done by Babel. But don’t worry. We’ll soon switch to compiling JSX at build-time rather than at runtime to get rid of the performance impact. Note that the code will not work on older browsers yet; you may get errors in the script babel.min.js.
The file index.html can be found under a directory called public in the GitHub repository; this is where the file will eventually end up.
Exercise: JSX
- 1.
Remove type="text/babel" from the script. What happens when you load index.html? Can you explain why? Put back type="text/babel" but remove the Babel JavaScript library. What happens now?
- 2.
We used the minified version of Babel, but not for React and ReactDOM. Can you guess why? Switch to the production minified version and introduce a runtime error in React (check out the unpkg.com website for the names of the production versions of these libraries). For example, introduce a typo in the ID of the content node so there is no place to mount the component. What happens?
Answers are available at the end of the chapter.
Project Setup
The server-less setup allowed you to get familiarized with React without any installations or firing up a server. But as you may have noticed, it's good neither for development nor for production. During development, some additional time is introduced for loading the scripts from a Content Delivery Network or CDN. If you take a look at the size of each of the scripts using the Network tab of the browser's developer console, you'll see that the babel compiler (even the minified version) is quite large. On production, especially in larger projects, runtime compilation of JSX to JavaScript will slow down the page loading and affect user experience.
So, let's get a little organized and serve all files from an HTTP server. We will, of course, use some other components of the MERN stack to achieve this. But before we do all that, let’s set up our project and folders in which we will be saving the files and installing libraries.
The commands that we will be typing in the shell have been collected together in a file called commands.md in the root of the GitHub repository.
Note
If you find that something is not working as expected when typing a command, cross-check the commands with the same in the GitHub repository ( https://github.com/vasansr/pro-mern-stack-2 ). This is because typos may have been introduced during the production of the book, or last-minute corrections may have missed making it to the book. The GitHub repository, on the other hand, reflects the most up-to-date and tested set of code and commands.
nvm
To start, let's install nvm. This stands for Node Version Manager, that tool makes installation and switching between multiple versions of Node.js easy. Node.js can be installed directly without nvm, but I've found that having nvm installed at the very beginning made my life easier when I had to start a new project and I wanted to use the latest and greatest version of Node.js at that point in time. At the same time, I did not want to switch to the newest version for some other of my large projects, for fear of breaking things in those projects.
To install nvm, if you are using Mac OS or any Linux-based distribution, follow the instructions on nvm's GitHub page. This can be found at https://github.com/creationix/nvm . Windows users can follow nvm for Windows (search for it in your favorite search engine) or install Node.js directly without nvm. Generally, I advise Windows users to install a Linux virtual machine (VM), preferably using Vagrant, and do all the server-side coding within the VM. This usually works best, especially because the code is finally deployed almost always on a Linux server, and having the same environment for development works best.
One tricky thing about nvm is knowing how it initializes your PATH. This works differently on different operating systems, so make sure you read up the nuances. Essentially, it adds a few lines to your shell's initialization scripts so that the next time you open a shell, your PATH is initialized and executes nvm's initialization scripts. This lets nvm know about the different versions of Node.js that are installed, and the path to the default executable.
For this reason, it's always a good idea to start a new shell right after you install nvm rather than continue in the shell that you installed it. Once you get the right path for your nvm, things follow smoothly.
You may choose to install Node.js directly, without nvm, and that is fine too. But the rest of the chapter assumes that you have installed nvm.
Node.js
The LTS version is assured to have support for a longer term than other versions. This means that although feature upgrades cannot be expected, security and performance fixes that are backward compatible can be expected. Also newer minor versions can be installed without worrying about breaking existing code.
Also, do make sure that any new shell also shows the newest version. (Note that the Windows version of nvm does not support the alias command. You may have to do nvm use 10 each time you open a new shell.)
Please ensure you don’t miss the –g flag. It tells npm to install itself globally, that is, available to all projects. To double check, run npm --version again .
Project
Before we install any third-party packages with npm, it's a good idea to initialize the project. With npm, even an application is considered a package. A package defines various attributes of the application. One such important attribute is a list of other packages that the application depends upon. This will change over time, as we find a need to use libraries as we make progress with the application.
Most questions that this command asks of you should be easy to answer. The defaults will work fine too. From now on, you should be in the project directory for all shell commands, especially npm commands (which I'll describe next). This will ensure that all changes and installations are localized to the project directory.
npm
Note
Specifying the major version alone (in this case 4) will suffice while installing a package. Which means that you may install a minor version that is not the same as what was used when writing this book. In the rare case that this is causing a problem, look up the specific version of the package within package.json in the GitHub repository. Then, specify that exact while installing the package, for example, npm install [email protected].
- 1.
All installations are local to the project directory. This means that a different project can use a different version of any of the installed packages. This may, at first, seem unnecessary and feel like a lot of duplication. But you will really appreciate this feature of npm when you start multiple Node.js projects and don't want to deal with a package upgrade that you don't need. Further, you will notice that the entire Express package, including all dependencies, is just 1.8MB. With packages being so tiny, excessive disk usage is not a concern at all.
- 2.
A package's dependencies are also isolated within the package. Thus, two packages depending on different versions of a common package could be installed, and they would each have their own copy and therefore work flawlessly.
- 3.
Administrator (superuser) rights are not needed to install a package.
There is, of course, an option to install packages globally, and sometimes it is useful to do this. One use case is when a command-line utility is packaged as an npm package. In such cases, having the command-line available regardless of the working directory is quite useful. In such cases, the –g option of npm install can be used to install a package globally and made available everywhere.
If you had installed Node.js via nvm, a global install will use your own home directory and make the package available to all projects within your home directory. You don’t need superuser or administrator rights to install a package globally. On the other hand, if you had installed Node.js directly, making it available for all users on your computer, you will need superuser or administrator rights.
At this point in time, it is a good idea to look at the GitHub repository ( https://github.com/vasansr/pro-mern-stack-2 ) again, especially to view the differences from the previous step. In this section we only added new files, so the only differences you will see are the new files.
Exercise: Project Setup
- 1.
When was package.json created? If you can't guess, inspect the contents; that should give you a hint. Still can't figure out? Go back and re-do your steps. Start with creation of the project directory and look at the directory contents at each step.
- 2.
Uninstall Express, but use the option --no-save. Now, just type npm install. What happens? Add another dependency, say, MongoDB, manually to package.json this time. Use version as “latest”. Now, type npm install. What happens?
- 3.
Use --save-dev while installing any new package. What difference do you see in package.json? What difference do you think it will make?
- 4.
Where do you think the packages files are installed? Type npm ls --depth=0 to check all the currently installed packages. Clean up any packages you do not need.
Play around with npm installation and un-installation a bit. This will be useful in general. Learn more about npm version syntax from the documentation: https://docs.npmjs.com/files/package.json#dependencies .
Answers are available at the end of the chapter.
Note
Although it is a good practice to check in the package.json.lock file so that the exact versions installed can be shared among team members, I have excluded it from the repository to keep the diffs concise and readable. When you start a team project using the MERN stack, you should check in this file in your Git repository.
Express
Express, if you remember in the introduction in the previous chapter, is the best way to run an HTTP server in the Node.js environment. For starters, we'll use Express to serve only static files. This is so that we get used to what Express does, without getting into a lot of server-side coding. We’ll serve the index.html file that we created in the previous section via Express.
require is a JavaScript keyword specific to Node.js, and this is used to import other modules. This keyword is not part of browser-side JavaScript because there was no concept of including other JavaScript files. All needed scripts were included directly in HTML files only. ES2015 specification came up with a way to include other files using the import keyword, but before the specification came out, Node.js had to invent its own using require. It’s also known as the CommonJS way of including other modules.
In the previous line, we loaded up the module called express and saved the top-level thing that the module exports, in the constant named express. Node.js allows the thing to be a function, an object, or whatever can fit into a variable. The type and form of what the module exports is really up to the module, and the documentation of the module will tell you how to use it.
In the case of Express, the module exports a function that can be used to instantiate an application. We just assigned this function to the variable express .
Note
We used the ES2015 const keyword to define the variable express. This makes the variable non-assignable after the first declaration. For variables that may be assigned a new value, the keyword let can be used in place of const.
Now that we have a handle to the application, let’s set it up. Express is a framework that does minimal work by itself; instead, it gets most of the job done by functions called middleware. A middleware is a function that takes in an HTTP request and response object, plus the next middleware function in the chain. The function can look at and modify the request and response objects, respond to requests, or decide to continue with middleware chain by calling the next middleware function.
The argument to the static() function is the directory where the middleware should look for the files, relative to where the app is being run. For the application we’ll build as part of this book, we’ll store all static files in the public directory under the root of our project. Let’s create this new directory, public, under the project root and move index.html that we created in the previous section to this new directory.
The first argument is optional and defaults to '/' if not specified, so we could skip it too.
Finally, now that the application is set up, we’ll need to start the server and let it serve HTTP requests. The listen() method of the application starts the server and waits eternally for requests. It takes in a port number as the first argument. Let’s use the port 3000, an arbitrary port. We won’t use port 80, the usual HTTP port, because to listen to that port we need to have administrative (superuser) privileges.
server.js: Express Server
Now, we are ready to start the web server and serve index.html. If you are looking at the GitHub repository for the code, you will find server.js under a directory called server. But at this point, the file needs to be in the root of the project directory.
You should see a message saying the application has started on port 3000. Now, open your browser and type http://localhost:3000/index.html in the URL bar. You should see the same Hello World page that we created in the previous section. If you see a 404 error message, maybe you have not moved index.html to the public directory .
The static middleware function served the contents of the index.html file from the public directory, as it matched the request URL. But it is also smart enough to translate a request to / (the root of the website) and respond by looking for an index.html in the directory. This is similar to what other static web servers such as Apache do. Thus, typing just http://localhost:3000/ is good enough to get the Hello World page.
When this is executed, npm looks for the file server.js and runs it using Node.js. So, let’s stop the server (using Ctrl+C on the command shell) and restart the server using npm start. You should see the same message saying the server has been started.
But what if we had a different starting point for the server? In fact, we want all the server-related files to go into a directory called server. So, let’s create that directory and move server.js into that directory.
package.json: Changes for Start Script
Thus, if the server starting point is anything other than server.js in the root directory, the full command line has to be specified in the scripts section of package.json.
Note that there is also a field called main in package.json. When we initialized this file, the value of this field was set to index.js automatically. This field is not meant for indicating the starting point of the server. Instead, if the package was a module (as opposed to an application), index.js would have been the file to load when the project is imported as a module using require() in other projects. Since this project is not a module that can be imported in other projects, this field is not of any interest to us, and neither do we have index.js in the source code.
Now, we can use npm start and see the familiar application started message and test it out one final time. At this point in time, it’s good to check out the GitHub repository and look at the diffs for this section. In particular, take a look at changes in an existing file, package.json, to familiarize yourself with how changes within a file are shown in the book and how the same changes are seen in GitHub as diffs.
Exercise: Express
- 1.
Change the name of the index.html file to something else, say, hello.html. How does this affect the application?
- 2.
If you wanted all static files to be accessed by a prefixed URL, for example /public, what change would you make? Hint: Take a look at the Express documentation for static files at https://expressjs.com/en/starter/static-files.html .
Answers are available at the end of the chapter.
Separate Script File
In all the previous sections, the transformation of JSX to JavaScript happens at runtime. This is inefficient and quite unnecessary. Let’s instead move the transformation to the build stage in our development, so that we can deploy a ready-to-use distribution of the application.
index.html: Separate HTML and JSX
App.jsx: JSX Part Separated Out from the HTML
At this point, the app should continue to work as before. If you point your browser to http://localhost:3000, you should see the same Hello World message. But we have only separated the files; we have not moved the transform to build time. The JSX continues to get transformed by the Babel library script, which was executed in the browser. We’ll move the transform to build time in the next section.
JSX Transform
Let’s now create a new directory to keep all the JSX files, which will be transformed into plain JavaScript and into the public folder. Let's call this directory src and move App.jsx into this directory.
If you look at the output directory public, you will see that there is a new file called App.js in there. If you open the file in your editor, you can see that the JSX elements have been converted to React.createElement() calls . Note that the Babel compilation automatically used the extension .js for the output file, which indicates that it is pure JavaScript.
index.html: Change in Script Name and Type
If you test this set of changes, you should see that things work as before. For good measure, you could use the browser's developer console to ensure it is App.js that's being fetched, and not App.jsx. The developer console can be found on most browsers; you may need to look at your browser’s documentation for instructions to get to it.
Exercise: JSX Transform
- 1.
Inspect the contents of App.js, the output of the transform. You’ll find it in the public directory. What do you see?
- 2.
Why did we use --save-dev while installing babel-cli? Hint: Read the npm documentation for the CLI command for install at https://docs.npmjs.com/cli/install .
Answers are available at the end of the chapter.
Older Browsers Support
I’d mentioned earlier that the JavaScript code will work in all modern browsers that support ES2015. But what if we need to support older browsers, for example, Internet Explorer? The older browsers don’t support things like Arrow Functions and the Array.from() method . In fact, running the code at this point in IE 11 or earlier should throw a console error message saying Object.assign is not a function.
App.jsx: Changes to Show the World with ES2015 Features
If you transform this using Babel, restart the server and point a browser at it. You will find that it works on most modern browsers. But, if you look at the transformed file, App.js, you will see that the JavaScript itself has not been changed, only the JSX has been replaced with React.createElement() calls . This is sure to fail on older browsers that neither recognize the arrow function syntax or the Array.from() method .
Now if you inspect the output of the transform, App.js, you will see that arrow functions have been replaced with regular functions.
That’s great, but how does one know which plugins have to be used? It’s going to be tedious to find out which browsers support what syntax and what transforms we’ve to choose for each. Fortunately, Babel does the job of automatically figuring this out via a preset called preset-env. This preset lets us specify the target browsers that we need to support and automatically applies all the transforms and plugins that are required to support those browsers.
Instead of using the command-line (which can get quite lengthy if we do use it), let’s specify the presets that need to be used in a configuration file. Babel looks for this in a file called .babelrc. In fact, there can be a .babelrc file in different directories, and the settings for files in that directory can be specified in each of those separately. Since we have all the client-side code in the directory called src, let’s create this file in that directory.
src/.babelrc: Presets Configured for JSX and ES5 Transform
If you run this command and then inspect the generated App.js, you will find that the arrow function has been replaced with a regular function and also that the string interpolation has been replaced with a string concatenation. If you take out the line ie: "11" from the configuration file and re-run the transform, you’ll find that these transformations are no longer there in the output file, because the browsers that we’re targeting already support these features natively.
But even with these transformations, if you test on an Internet Explorer version 11, the code will still not work. That’s because it’s not just the transformations; there are some built-ins such as Array.find() that just aren’t there in the browser. Note that no amount of compilation or transformation can add a bunch of code like the Array.find() implementation. We really need these implementations to be available at runtime as a library of functions.
index.html: Changes for Including Babel Polyfill
Exercise: Older Browser Support
- 1.
Try to format the message as one per line by using <br /> to join the individual messages instead of a space. Are you able to do it? Why not?
Answers are available at the end of the chapter.
Automate
Apart from being able to start the project using npm start, npm has the ability to define other custom commands. This is especially useful when there are many command line parameters for the command and typing them out on the shell becomes tedious. (Didn’t I say npm was powerful? This is one of the things that it does, even though this is not real package manager functionality.) These custom commands can be specified in the scripts section of package.json. These can then be run using npm run <script> from the console.
After this, if you run npm start again to start the server, you can see any changes to App.jsx reflected in the application.
Note
Avoid using npm sub-command names that are also npm first-level commands such as build and rebuild, as this leads to silent errors if and when you leave out the run in the npm command.
It is essentially the same command as compile, but with two extra command line options, --watch and --verbose. The first option instructs Babel to watch for changes in source files, and the second one causes it to print out a line in the console whenever a change causes a recompilation. This is just to give a reassurance that a compile has in fact taken place whenever a change is made, provided you keep a watch on the console running this command.
Package.json: Adding Scripts for Transformation
If you now run the new command using npm run watch , you will notice that it does one transform, but it doesn’t return to the shell. It’s actually waiting in a permanent loop, watching for changes to the source files. So, to run the server, another terminal is needed, where npm start can be executed.
If you make make a small change to App.jsx and save the file, you’ll see that App.js in the public directory is regenerated. And, when you refresh the browser, you can see those changes without having to manually recompile. You can also make any changes to server.js and see that the server starts, with a message on the console that says the server is being restarted.
Summary
In this chapter, you learned the basics of how React applications can be built. We started with a simple piece of code written in React JSX that we compiled at runtime, then we moved the compilation as well as serving the file to the server.
We used nvm to install Node.js; you saw how npm can be used not just to install Node.js packages, but also to save command-line instructions in conventional or easy-to-spot scripts. We then used Babel, to transpile, that is, transform or compile from one specification of the language to another to support older browsers. Babel also helped us transform JSX into pure JavaScript.
You also got a whiff of what Node.js with Express can do. We did not use MongoDB, the M in the MERN stack, but I hope you got a good view of the other components of the stack.
By now, you should also have gotten familiar with how the book’s GitHub repository is organized and the conventions used in the book. For every section, there is a testable set of code that you can compare your own typed out code with. Importantly, the diffs between each step are valuable to understand the exact changes that were made in each step. Once again, note that the code in the GitHub repository is the one to rely on, with up-to-date changes that couldn’t make it to the printed book. If you find that you have followed the book verbatim, yet things don’t work as expected, do consult the GitHub repository to see if the printed book’s errors have been corrected there.
In the next two chapters, we’ll dive deeper into React, then surface up to look at the big picture, dealing with APIs, MongoDB, and Express in later chapters.
Answers to Exercises
Exercise: Server-less Hello World
- 1.
To specify a class in React.createElement(), we need to use { className: <name>} instead of {class: <name>}. This is because class is a reserved word in JavaScript, and we cannot use it as a field name in objects.
- 2.
The element variable contains a nested tree of elements, which reflects what the DOM would contain. I would call this a virtual DOM, which is what it is indeed popularly referred to as.
Exercise: JSX
- 1.
Removing the script type will cause the browser to treat it as regular JavaScript and we will see syntax errors on the console because JSX is not valid JavaScript. Removing the Babel compiler instead will cause the script to be ignored, since the browser does not recognize scripts of type text/babel, and it will ignore it. In either case, the application will not work.
- 2.
A minified version of React hides or shortens runtime errors. A non-minified version gives full errors and helpful warnings as well.
Exercise: Project Setup
- 1.
package.json was created when we created the project using npm init. In fact, all our responses to the prompts when we ran npm init were recorded in package.json.
- 2.
When using --no-save, npm keeps the file package.json unchanged. Thus, package.json would have retained the dependency of Express. Running npm install without any further options or parameters installs all dependencies listed in package.json. Thus, you could add dependencies manually to package.json and just use npm install.
- 3.
The --save-dev option adds the package in devDependencies instead of dependencies. The list of dev dependencies will not be installed in production, which is indicated by the environment variable NODE_ENV being set to the string production.
- 4.
Package files are installed under the directory node_modules under the project. npm ls lists all the packages installed, in a tree-like manner. --depth=0 restricts the tree depth to the top-level packages. Deleting the entire node_modules directory is one way of ensuring you start clean.
Exercise: Express
- 1.
The static file middleware does not specially treat hello.html as it did index.html, so you will have to access the application with the name of the file like this: http://localhost:3000/hello.html.
- 2.
For accessing static files via a different mount point, specify that prefix in the middleware generated helper function as the first parameter. For example, app.use('/public', express.static('/public')).
Exercise: JSX Transform
- 1.
App.js now contains pure JavaScript, with all JSX elements converted to React.createElement() calls. We couldn’t see this transform earlier when the transform happened in the browser.
- 2.
When we deploy the code, we will only deploy a pre-built version of the application. That is, we will transform the JSX on a build server or our development environment and push out the resulting JavaScript to our production server. Thus, on the production server, we will not need the tools that are required to build the application. Therefore, we used --save-dev so that, on the production server, the package need not be installed.
Exercise: Older Browsers Support
- 1.
React does this on purpose, to avoid cross-site scripting vulnerabilities. It is not easy to insert HTML markup, although there is a way using the dangerouslySetInnerHTML attribute of an element. The correct way to do this would be to compose an array of components. We will explore how to do this in later chapters.