Node.js Tutorial: How JavaScript on the backend can make your life easier.

Node.js is JavaScript on the backend, built around the highly optimized google's V8 javascript engine. Welcome to the world of asynchronous non-blocking programming.

Node.js excels at:

  • High availability API's, the asynchronous model reduces the amount of processor time being wasted in idle while waiting for external resources to be processed.

  • Pub-Sub systems like a chat server using WebSockets through

  • High availability Proxy services.

  • Web Crawlers! Since Node.js is JavaScript on the server, you can do pretty cool stuff such as using a headless browser, through a tool like Puppeteer.

  • Develop desktop applications using Electron!

What Node isn't that good at:

  • Intensive processing on the server, if you have a task that you need to crunch, 9 times out of 10 there is a better tool around.

What you will learn#

By the end of this tutorial you will have a good grasp on the following:

  • How to use NPM, and understand how your Package.json file works

  • Understanding the basics of a Node.js application

  • Asynchronous programming using promises and the event loop

  • Building basic Node.js API using express

  • Using Node standard library, and the file system package

  • Try Catch error handling with Node

Prerequisites - What You Need To Know#

For this article, we assume that you're familiar with JavaScript basics, such as working with promises

For more on JavaScript, check out these interesting articles to enhance your grasp on the subject.

Step 0: Installing Node.js#

Node.js is available for most platforms.

If you are using brew on macOS you can install it as follows:

For Windows, Linux, macOS and other operating systems, you can download the Node binaries from here

Or you could use NodeSource to get your platform-specific binaries.

Step 1: Writing our Hello world with Node.js and understanding the basics of asynchronous programming.#

In an empty directory create a file named app.js with a single line of code as follows

To run it, in your shell of preference just CD to the directory, and type:

And just as magic you will get an output in the console saying hello.

The main point to notice here is that this is JavaScript we output to the console using mostly the same interface that we would use in the browser, and also, that immediately after that line of code executed, our application ends.

Let's use the Node.js standard File System module fs, to write our hello world into a file:

Let's take a moment, to explore in-depth the lifeCycle of a Node application:

  • First Node.js loads into their respective scopes all the functions, including the Node internal functions like require() , and user-defined functions like helloWorld(), because of this you can use functions before they are declared as long as you are within the same scope, give it a try moving the call to helloWorld() from line 9 to line 2.

  • The event loop gets initialized, this is what handles Node.js asynchronous events which allow performing non-blocking I/O operations — despite the fact that JavaScript is single-threaded — it does so by offloading operations to the system kernel whenever possible, we will go into further detail about the inner workings of the event loop.

  • Node Processes the provided input script, in this case, app.js, each time that either on our code or any library we import an asynchronous call gets started, Node will allocate such call into the event loop.

  • After the input script is finished being processed ( app.js in this case), then Node start the processing of the event loop and the asynchronous events allocated in it, in the following order of the following phases (described in the image in the right)

    • timers: this phase executes callbacks scheduled by setTimeout() and setInterval().

    • pending callbacks: executes I/O callbacks deferred to the next loop iteration.

    • idle, prepare: only used internally.

    • poll: retrieve new I/O events; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate()); Node will block here when appropriate.

    • check: setImmediate() callbacks are invoked here.

    • close callbacks: some close callbacks, e.g. socket.on('close', ...).

  • Between each run of the event loop, Node.js checks if it is waiting for any asynchronous I/O or timers and shuts down cleanly if there are not any.

The entirety of the event loop is running within the same single thread!

Node.js is indeed single-threaded!

Following the Node application lifecycle, let's track the execution flow of the node application we just created, in the comments the "#" will outline the order of execution, please follow the # order:

If we decided to remove the await from line 13, the entire flow would change, the way await works is that it transforms the current async function, into a promise.
If we removed the await from line 13, line 14 would just not wait for the fs.writeFile() operation to be finished, and process immediately, and instead of the rest of the function being stacked in the event loop, only the fs.writeFile() operation would be stacked.

Step 2: Writing our first API with Node.js Express.js#

With your install of Node.js, you will have the command line tool NPM, it is a package manager for Node, you can easily use it to download and install new modules, to import into your application.

For each Node application, you want to have a package.json, this file will hold information about your project, Author details, version, how to execute it, custom NPM commands that you define.

package.json also contains a list of all your dependencies, that way if someone wants to use your application you only have to distribute the source, and using NPM all the dependencies can be easily installed.

To initialize the package.json file, run the following:

Follow the steps the command line, set the Author details, and the main entry point as index.js, the rest you can leave it as default.

To install a package using npm we do: npm install <name of the package> we also add the --save flag to save our new added dependencies to our package.json file.
Now let's follow that and install our first npm module Express:

Express is a Fast, unopinionated, minimalist web framework for Node.js

Create a new file index.js, with the following source:

We run our web app, with the following command:

If you go to your localhost http://localhost:3000/ you will get the response we coded:

A fun thing to consider, this time after we ran our application, it didn't automatically close! that's because of the following code:

app.listen() attaches a listener into the poll phase of the event loop, that listens for HTTP calls in the declared port, and unless we manually detach it, is going to be always up, and on each iteration of the event loop, is going to stay alive listening for new HTTP calls to invoke our callback function on each one.

Step 3: Expanding our API, to read and write to a file on each call#

Let's add a new route, to our API, that on each call uses fs.readFile() to read into the file system, get the contents of the helloworld.txt file, and send it back as the response of the request.

Also lets change, change the original / endpoint to return JSON instead of a plain text

Our API is looking pretty nice, but what would happen if you removed the helloworld.txt and then attempted to call the /readFile API endpoint? Oh no, we got an error, and since the error is thrown on line 14, we never get to send a response back, so the browser would be waiting until the requests timeouts

The error in question would be:

(node:5288) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open 'helloworld2.txt'

(node:5288) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)

(node:5288) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process

with a non-zero exit code.

This error is caused by attempting to reading a nonexistent file with fs.readFile.

Step 3: Error Managing in Node done the right way.

In javascript when an Error is thrown, the execution of the current code block gets aborted, so to not lose control of the flow, we need to catch the error, error managing can be done elegantly in javascript using Try-Catch blocks, let's apply that, to our API endpoint.

You should always catch your errors!


We've been through a lot, within this short post, but by now you should have a well-rounded understanding of how a Node.js application works.
Have fun implementing your own API's!

Have fun and keep coding!

More Resources#

Check out the documentation of the modules we used in this post:

Getting Help (and Feedback)#

If you have any questions - or want feedback on your post - come join our community Discord. See you there!