Node.js Basics

Daniel Wagener
Nov 3 · 6 min read

These are my notes for Jonas Schmedtmann’s Node.js Bootcamp on Udemy: https://www.udemy.com/course/nodejs-express-mongodb-bootcamp/

Notice: I went through this course using an Ubuntu terminal.

What is Node.js?

Node.js is a Javascript runtime built on Google’s open-source v8 Javascript engine. Traditionally, Javascript’s runtime (i.e. environment) is the browser. Node.js is a container that allows Javascript to run outside of a browser. The v8 engine is responsible for actually executing that Javascript. With Node.js, we can use Javascript on the server side of web development.

Why Node.js?

Node.js is a single-threaded, based on an event-driven, non-blocking I/O model (elaboration on these terms down below!). It’s perfect for building fast and scalable data-intensive apps. Node.js is great for an API with a database behind it (preferably NoSQL), data streaming (think YouTube), a real-time chat application, or a server-side web app. However, it is not suitable for anything that requires heavy server-side processing (e.g. video conversion).

Using Node.js

Assuming we have Node.js installed our machine, we can just type node into the terminal to start the REPL (Read-Eval-Print-Loop). Inside the REPL, we can declare variables just like we would in a browser console.
To reference the most recently logged value, we can use an underscore:

To run a local external Javascript file in the REPL, we just enter node NAME-OF-FILE.js into the terminal.

Reading and writing files

One of the ways to read a file is to use the readFileSync() method in the fs module (‘fs’ standing for ‘file system’). This function takes two arguments: a file path and a character encoding (usually "utf-8" for English).

We can then take the contents of that file and use it to write a new file by using writeFileSync(), which takes an output path and some text as its arguments:

Blocking and non-blocking

The above code snippets are examples of synchronous code: each line of code is executed only after the line before has finished running. We also call this “blocking,” as each line blocks the next line from running until its own code has finished. Because blocking can create performance issues, we instead write non-blocking code in which heavy operations have callback functions to be executed after the operation has finished.

What exactly are the performance issues of blocking code? Node.js is single-threaded, meaning all its processes happen in one part of the CPU. If thousands of users are using our app at once and one user makes a heavy request, blocking code will force all other users to wait for that request to finish before Node.js can process those other users’ requests. Far from ideal. With asynchronous code, we can have that heavy task running in the background while other users continue to use the app. To achieve async behavior, Node.js is heavily reliant on callback functions.

(Side note: PHP is multi-threaded and thus has an entire differently paradigm for asynchronous performance).

A simple example of non-blocking code:

A Simple Website with Node.js

This project takes product data from a JSON file and uses it to create web pages. We’ll configure Node.js to create new HTML files and elements by using the HTML templates we provide it.

Creating a server

To create a web server, we first have to require Node’s http module. Then, we call http.createServer and assign it to a server variable. We then call server.listen with two arguments: a port number and an IP address. The default IP address for local hosts is in this example:

Routing

By accessing the url property on the response object, we can make our server respond to differently URLs:

However, if the user tries to visit a path other than the one we’ve specified, the server get stuck and not know what to do. Therefore, we need to add a fallback:

We have the ability to pass an object of response headers as the second argument to writeHead. If we set Content-type to text/html, we can format our error message:

Creating an (extremely simple) API

First, we create a new route and read our JSON file.

Note that instead of reading the file from ./dev-data/data.json, we’ve used the __dirname variable. Because . refers to the current working directory, the code will break if we run it from another location. Therefore, it’s a better practice to use __dirname, which points to the directory in while the file is located. Then, we use the built-in Javascript method JSON.parse() to turn the JSON into a Javascript object.

Then, we need to send back a response. For now, we’ll just send the unparsed JSON:

However, this code is inefficient because the JSON has to be read anew every time someone access our /api path. So, we’ll refactor the code so that the JSON is only read once, outside of the callback functions. We’ll use a synchronous file read here, which is fine since this top-level code only runs once.

Creating and filling HTML templates

To prepare our HTML for data insertion, we have to add placeholders to our markup. We can use any syntax we like, so long as it’s unique in the file. In this case, we’ll use something reminiscent of Jinja:

Note that we can also put placeholders inside attributes:

After we’ve created out HTML templates, we need to fill them with data from our JSON. We start by reading our two HTML templates: the overview template and the cards template from which we’ll create an array of cards to insert into the overview. We read these templates synchronously (outside of the callbacks) because they only need to be read once. Then, we start populating our placeholders:

Outside of our callbacks, we’ll define replaceTemplate(). We use regular expressions to take advantage of the global flag, in case a placeholder appears more than once on the page:

Because the map method outputs an array, we join the array items together to create one long string we can then insert into our overview template:

The final product looks something like this:

Parsing variables from URLs

Eventually, we’ll want each product to have its own page. Rather than manually code each product page, we want to dynamically generate pages based on the data in our API. We first require the url module at the top of our file, invoke its parse() method, and destructure a couple properties off the result (one to replace the pathName variable from earlier).

The query property extracts key-value pairs from the URL. For example, at the URL http://127.0.0.1:8000/product?id=0, the value of query will be { id: '0' }. Since we’ve already parsed the JSON from our API and stored it in a variable called dataObj, we can use the query object to retrieve products by ID:

From there, we just need to create a response header and then use the same replaceTemplate() function from before.

Making our own modules

In Node.js, every Javascript file is treated as a module. So, if wanted to extract our replaceTemplate() function for use in other files, we could first create a new folder called modules and a file called replaceTemplate.js. We would then assign the contents of that function to module.exports:

Then, we can import this module at the top of our file:

The final product

Even though the course didn’t require it, I decided to rewrite the routes to use slugs derived from the product names instead of numerical indices. The final index.js file looks like this:

Globally Installing npm Packages

If we use Node.js out of the box, we have to manually restart our server any time we make changes to our code. A package called nodemon restarts our server automatically every time we save a change. This package is so convenient, we might want to configure npm to install nodemon every time we run npm init. The terminal command for this is sudo npm i nodemon --global ( the i is shorthand for install). Once we’ve installed nodemon globally, we can run our code with nodemon index.js.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade