What do you want to learn?
Skip to main content
Node.js: Getting Started
by Samer Buna
The Node.js runtime powers back-end servers for big players like PayPal, Netflix, LinkedIn, and even NASA. This course will teach you the fundamentals of this very popular runtime and get you comfortable writing code for Node.
Start CourseBookmarkAdd to Channel
Table of contents
What Is Node?
Some Analogies About Node
What You Get When You Install Node
On the course review slide, there are references to folders associated with the modules of this course. These folders can be found on GitHub or under the exercise files section. Here is the GitHub repository that I'll be using throughout this course. It will give us starting points for code templates and let us focus our time on concepts rather than wasting time typing simple scripts. This repo is hosted under GitHub.com/jscomplete/ngs. Ngs here is Node getting started. You can see the different folders for the different modules right here. The same content of this repo is also available under the exercise files on the course page, if you don't have access to GitHub. Your first step is to clone this repo locally to your machine. To clone this repo, copy the ngs repo URL in here and type the following command in your terminal, git clone, the repo URL. This will create a direct renamed ngs under your current working directory. So cd into this new ngs, and in here you can see the list of numbered folders. Let me open up an editor here on this repo. So within the top level folders, which are associated with modules in the course, you'll see folders and files representing videos of the course module in order. However, some of the course videos will not have folders and some folders here might have multiple files representing the examples we are going to cover in its associated video. The files are usually numbered in the order of their example in the video. And all these files in the ngs repo is your starting point for each exercise we are going to go through in this course. One thing I'd like to point out before we begin, this course has many modules. We use the word module here to describe a section in the course. The word module is also heavily used with Node to refer to a file or folder of code. To make this a little bit less confusing, I will say course module every time I refer to a section of the course. So let's jump to the course module and get you properly introduced to the wonderful Node.js runtime.
Getting Started with Node
Node’s REPL Mode
TAB and Underscore
Here is Node Hello World example. This simple script represents a simple web server. You don't need to install anything to run this script. This is all Node's built in power. Don't worry about figuring out what's going on in this script just yet. We'll get there eventually. To execute this script with Node, you just specify the location of the script as the argument for the Node command. You can actually see the location of this script within the projects folder, right here in the corner for my atom editor here. And I can also click this region here to copy the full path for this file. So now I'll go ahead and paste this full path right here after the Node command and this will execute the file. As you can see, it reported back that server is running. The script location that you specify for a Node command can also be relative. So if I'm inside this 1-getting-started, 1-executing-scripts directory and I specify node 1-hello-world, just like that, Node will look for the file in the current working directory. If the script that we're executing has a running task, like a web server listening for connections, for example, then Node will continue running. Note that if the script does not have a running task, like this other script here that just prints a message, Node will execute this script and exit immediately after that. And Node process exiting is a normal thing in a Node cluster. Node will not idle and use resources unnecessarily. Okay because to the simple web server script, and we'll execute it again. So now this simple web server script is running and its task is active, it listens for all the HTTP connections. However, this Node process will only use V8 when there are HTTP connections, otherwise V8 will remain idle. Let's decipher this simple web server. The first line here is using the require function. This is the first thing you need to learn about Node's internal. The require function is what you use to manage the dependencies of your programs. You can use require to depend on any library, whether this library is a built-in one, like HTTP here, or if it's a third party installed library. This program here depends on the built-in HTTP module. It's the module that has the features of creating a web server. There are many other libraries that you can use to create a web server, but this one is built-in. You don't need to install anything to use it, but you do need to require it. Remember that when we were in Node's REPL, this library was available immediately without needing to require it. This is not the case with executable scripts, like this one. You can't use any dependencies without requiring them first. This line here creates a server constant by invoking the createServer function on the HTTP module. This function is one of the many that are available under the HTTP module API. You can use it to create a web server and it accepts an argument that is known as the request listener. This is a simple function that Node will invoke every time there is a request to the created web server. This function is also known as a callback, but this term is a bit old at this point. Remember this argument as a listener. This server will listen to requests and it will invoke the listener function for each request. And this is why this listener function receives the request object as an argument. It's named req here, but you can name it whatever you need. The other argument this listener function receives, named res here, is a response object. It is the other side of a request connection. We can use this object to write things back to the requester. It's exactly what this simple web server is doing. It's writing back using the .end method and the Hello World string. We'll learn about the .end method later in this course, but it can be used as a shortcut to write data and then end the connection. The createServer function only creates the server, it does not activate it. To activate this web server, we need to invoke the .listen function on the created server. This function accepts many arguments, like what OS port to use for this server. The last argument here is a function that will be invoked once the server is successfully running on that port. This example just logs a message to indicate that the server is now running. This listen function is what actually keeps the Node process running. It's the task that will keep the Node runtime busy and not exit. While the server is running, if we go to a browser and ask for an HTTP connection on localhost with the port that was used in this script, 4242 in this case, we will see the Hello World string that this example had in its request listener. Go ahead and try to change this line and return something else. You will need to restart the Node process to make that change work. Just kill the process with Ctrl+C, use the up arrow to get the last executed command, and execute that again. If you refresh the web browser now, you should see the newly returned string. When we build a full web server example, I'll show you how to avoid needing to restart the server every time you change something because this will be an annoying thing to keep doing manually.
Working with Timers
Some of the popular global functions in a browser environment are the timer functions, like setTimeout and setInterval. Node.js has an API for these functions as well, and it exactly matches the browser's API. These timer functions can be used to delay or repeat the execution of other functions, which they receive as arguments. For example, this code uses setTimeout to delay the printing of this greeting message by 4 seconds. The second argument to setTimeout is the delay in millisecond. This is why we are multiplying 4 by 1000 here to make it into 4 seconds. The first argument to setTimeout is the function who's execution will be delayed. If we execute this script, normally with the Node command, Node will pause for 4 seconds and then it'll print the greeting and exit after that. Note that this first argument to setTimeout is just a function reference. It does not have to be an inline function like this. For example, this code here will do the same job, but it uses a function defined before setTimeout. Note that if the function that we pass to setTimeout receives argument, like this example here, argument 1, 2, 3, and more, then we can use the remaining arguments in setTimeout to pass these arguments to the delayed function once it's executed with setTimeout. Here's an example. Take a look at this code and try to figure out what it will do. The rocks function, which is delayed by 2 seconds, accept a who argument and our setTimeout calls relays the value Pluralsight as the who argument. Executing the script will print out Pluralsight rocks after 2 seconds. Time for a challenge. Ready? Using what you learned so far about setTimeout, print the following two messages after their corresponding delays. Print the message "Hello after 4 seconds" after 4 seconds, then print the message "Hello after 8 seconds" after 8 seconds. This would be an easy challenge without constraints, but we are not here to learn just the easy stuff. I have a constraint for you. You can only define a single function in your script, which includes inline functions. This means many setTimeout calls will have to use the exact same function. Okay, pause here and try it out. I really hope that you will try to do these course challenges because, trust me on this, it is the best way to get comfortable with what you're learning. If you just watch, you'll learn, but it will be harder for you to retain and improve. You do that when you start pushing your limits and put yourself outside your comfort zone. Here's how I'd solve this challenge. I've made theOneFunc receive delay argument and used that delay argument in the printed out message. This way the function can print different messages based on whatever delay we pass to it. I then used theOneFunc in two setTimeout calls, one that fires after 4 seconds, and another that fires after 8 seconds. Both of these setTimeout calls also get a third argument to represent the delay argument for theOneFunc. Executing this script will print out the challenge requirements. The first message after 4 seconds and the second message after 8 seconds. But what if I ask you to print the message every 4 seconds forever? While you can put setTimeout in a loop, Node offers the setInterval as well, which would accomplish exactly that. Just use setInterval instead of setTimeout. In this example, this code will print its message every 3 seconds. Node will print this message forever until you kill the process with Ctrl+C. Another cool thing about these timers is that you can cancel them with code. A call to setTimeout returns a timerId and you can use it with a clearTimeout call to cancel that timer. Here's an example. This simple timer here is supposed to fire after 0 milliseconds, making it immediate, but it won't because we are capturing the timerId and cancelling it right after with a clearTimeout call. When we execute this script, Node will not print anything and the process will just exit. By the way, there is another way to do setTimeout with 0 millseconds, the timer API has another function, it's called setImmediate, and it's basically the same thing as a setTimeout with a 0 millisecond, but we don't have to specify a delay here, it's an immediate thing. We'll see a practical case for this function later in the course. And just like clearTimeout, there is also a clearInterval, which does the exact same thing but for set interval calls, and there is also a clearImmediate, which does the same thing for setImmediate calls. So as you can hopefully see from this example, executing something with setTimeout after 0 millseconds, does not mean execute it right away, but rather it means execute it right away after you're done with everything else in this script. Let me make this point clear with an example. Here's a simple setTimeout call that should fire after half a second, but it won't. Right after defining the timer, we block Node synchronously with a big loop. This is 1 with 10 zeros in front of it, so this is a 10 billion ticks loop, which basically simulate a busy CPU. Node can do nothing while this loop is ticking. This is, of course, a very bad thing to do in practice, but it'll help you here to understand that setTimeout delay is not a guaranteed thing, but rather a minimum thing. This 500 millisecond here means a minimum delay of 500 milliseconds. In reality, this script will take a lot longer to print its greeting line. It will have to wait on the blocking loop to finish first. Let's do one more challenge on timers. Print the message "Hello World" every second, but only 5 times. After 5 times, print the message "Done" and let the Node process exit. And you cannot use a setTimeout call for this challenge. Little hint, you need a counter here. Okay, you can pause here. Hopefully this was an easy one. I initiated a counter value as 0 and then started a setInterval call, capturing its Id. The delayed function will print the message and increment the counter each time. Inside the delayed function, an if statement will check if we're at 5 times by now, if so, it will print Done and clear the interval using the captured interval constant. The interval delay is 1000 milliseconds. In this file here, I've put a couple more challenges for you to practice timers. I will not solve these challenges here, to keep the course short, but I wrote an article on Medium about them, which you can read at this URL. I've also included the solutions in this course's folder.
Node’s Command Line Interface
The “process” Object
You can use the Node command with custom environment variables, for example, we can do something like VAL1=10 VAL2=20, note, no commas here, then continue to execute a Node script like this. In the Node script, we can access the values in the environment variables using the process object, which has many properties, but the one that you can use for this purpose is the env property, which is an object that has all the env properties available through the operating system, like user here, it also includes the one we just customized, like VAL1 and VAL2. You can export environment variables prior to executing a script and Node will read those as well. For example, instead of this one liner, we can do something like export VAL1=100, export VAL2=200, and then we can execute this script normally and it will pick up our 100 and 200 values. This is a handy bridge between the operating system and the Node process. You can use it to communicate dynamic configuration values. For example, if you want your script to run on development port 4242, but in production you want it to run on port 80 instead, you can use process.env to make the port dynamic and control it on different machines. There is another way to pass information for the execution context of the Node process and that's through the process.argv array. This array will have an item for every positional argument you specify when executing the Node script in order. For example, if we have a Node command to print process.argv, using the handy -p CLI option, all the arguments after this command will be set in the resulting array as strings, even if you pass numbers, this array will always assume strings. The first item here in the resulting array is the location of the Node command, and if we're executing a script, the second item in this array would be the script name, but in this case we're not executing a script, so the remaining items in this array are the arguments that we passed to the Node command. And they're all strings in this array. This is a cool feature, but I think I prefer the process.env method because I get to name the passed values there. With argv, we'd have to do more tricks to accomplish this named values feature. Other properties you should be aware of on this special process object are the standard input output streams. There are three of them, stdin for input, stdout for output, and stderr for error messages. These control the communication channel between the Node process and its operating system execution environment. We've been actually using them under the hood. When you use a console.log line, that line writes to the stdout stream. In fact, you can accomplish the same functionality of console.log by using a process.stdout.write line, just like this. Stdin can be used to read information from users. Here's an example to do that. The std I/O objects are all streams, which is a topic that we are yet to explore, but the gist of it is that we use events and methods to use these streams. In here we are listening for a readable event and using the read method to read a chunk of data, and then we print out the same chunk to stdout, making this effectively an echo utility. It will echo everything you type to it. There are multiple ways to consume and benefit from these I/O streams, and streams in general. For example, the same echo example can be done using the excellent pipe function that's available on readable streams, like process.stdin. We pipe a readable stream into a writeable one, like process.stdout, using the argument for the pipe function, and this makes the exact same echo utility. We'll have a bit more to learn about streams later in this course, but for now, just make a mental note that streams are awesome and you should utilize them in every possible way. Nodes process object can also be used to terminate the process or do something when the process is terminate unexpectedly. Here's an example of that, this code has a timer that will fire after 2 seconds and it will call the exit function on the process object. This will manually terminate the process and make Node exit. As Node is exiting the process, it looks for any listeners registered on the exit event. We have done exactly that in here, which means right before Node exits, it will execute this function to print out this message. Because of the nature of asynchronous code in Node, this Hello line will be executed first, then the timer will go next, and the exit listener will fire. This simple example demonstrates the power of Node asynchronous nature and it's event-based methodology. We will learn more about that in the concurrency module of this course.
EcmaScript and TC39
Variables and Block Scopes
Destructuring and Rest/Spread
Promises and Async/Await
Node is event driven. Most of the functions that you'll be working with in Node return promises, and you'll have to consume them using the promise syntax with .then and .catch. However, a more preferable way to consume promises is using the new async/await syntax, which makes your promise consuming code a bit more readable and easier, especially when you start dealing with loops and other complexities. Here's an example code that is consuming a Promise object using the regular Promise syntax. Here we have a simple fetch function that reads an HTTPS response for a URL. Don't worry about the implementation of this function, just notice how it returns a Promise object. This is a modern alternative to using callbacks for the async nature of this function. To consume the fetch function, we use the .then syntax, which will expose the data available after the async action. Alternatively, we can also consume any promise using the async/await feature as seen here. We use the keyword await before the promise and that will give us access to the data available after the async action. We can use this data directly after the await line, just like this, which is a lot simpler than callbacks and using .then as well. However, to make this await feature work, you have to wrap your code with a function labeled with the async keyword and then call the function to execute the async action. Testing this code now, the same fetch promise will be consumed twice, once with the regular .then syntax, and another time with the new async/await syntax. The async/await syntax is definitely easier to read and it will make your life especially easier if you need to work with promises that depend on each other or promises that need to be within the loop.
NPM: Node Package Manager
What Exactly Is NPM?
The NPM Command
As a Node developer, you will be working with the npm command a lot. This command comes installed with Node itself, so you don't need to do anything to install it. If you have Node installed, you should have the global npm command installed as well. You can check the version with -v. Npm gets updated more frequently than Node, so sometimes you might need to update npm itself separately from Node to get the latest release. You do that using the command npm install -g, for global, npm. Yep, you can use npm to update npm. If you get an EACCESS error here, that means you most likely installed Node itself with some admin privileges. You can use sudo in that case or start a whole terminal with admin privileges. On Mac OS, if you install Node through Homebrew or NVM, you usually don't need to sudo any Node or npm commands. If you are facing an EACCESS error, you can also fix the npm permissions so that you don't have to run npm with sudo. There's an article in the npmjs documentation site, right here, that has some details on that. Let's quickly explore the first command of npm, the one that we just used, install. To do that, I'm going to create a test directory, test-npm, cd into that, and run npm install, and by the way, install has a shortcut, you can just do npm i, which is cool. And we're going to install one package called express. So this npm install command is our client that just downloaded the package from its source, which by default is the npmjs.com registry itself, but you can configure the command for other sources. And after it downloaded that package, npm placed the package in a special folder under the project named node_modules. So this is the process that's known as installed a package, but there is nothing magic about it really, it just places the package under this node_modules oflder. Node itself is designed to use this node_modules folder by default to look for any packages it needs to use. When Node needs to require a package, like express here, it does not communicate with npm at all, it just uses the node_modules folder and the packages that were placed inside that node_modules folder. Did you notice how when I installed the express package here, npm warned me that this folder does not have a package.json file? This package.json file is important. So let's redo this example with a package.json file. I have a folder in the 3-npm folder here called 1-npm-command, and this folder has the package.json file. So let's cd into this folder and redo the npm install express command and note what happens to the package.json file. Did you see that? The package.json file had a new section here. This is the section where npm is documenting this new dependency for this project, express. And not only that, the npm install process will also take care of installing any sub-dependencies for the main install package. Take a look at what you now should see under the node_modules folder. Although we asked npm to install a single dependency, express, npm installed many other dependencies. What exactly are these? These are the packages that express itself depends on, and since we made our project depend on express, these other packages are now in the project's sub-dependencies. Okay so let me quickly review what happened when we invoked the command npm install express. Npm first created a new node_modules folder because that folder did not exist before. The first npm install will do that. Npm then downloaded the express package from npmjs.com and placed it under the newly created node_modules folder. It then modified the package.json file to document this exact dependency and the version used for this dependency, and it also created a package-lock.json file. So let's talk about these two files, package.json and package-lock.json.
The package.json and package-lock.json Files
The package.json file is the one file that you'll see in every npm package. It's a JSON file that can be used to provide information about a package and it's required by npm. This file is mostly modified by the npm command itself, but in a few cases, you'll need to manually edit this file as well. In the previous example, we started with a simple package.json file that only had the required properties, name and version. The name of an npm package is its unique identifier. If you need to publish this package, that name has to be unique, and not used before, across the whole registry. The version property is a semantic versioning string. We'll talk about this string in the next video. When we installed the express dependency, npm automatically modified our package.json and added a dependencies section documenting the version of express that it used. Here is the package.json file for the popular express package. As you can see, this file includes meta information about express, things like description, license, and key words, but the most important information in this file is the dependencies section. These are the packages that express depends on and this is the same list of packages that we got when we installed express locally in the previous test. This is really the most important benefit of the package.json file. This file makes the building of the project dependencies a reproducible task. This means by sharing this package.json file with other developers or with build servers, the process of building the project dependencies on these other machines can be automated through the dependencies section of the file here. Let me show you how that works with a local example. Let me add one more dependency in the 1-npm folder example here. This time let's add the lodash package. The command that we need here is npm install lodash. Npm is now downloading lodash and it just placed it under the node_modules folder and updated the package.json file to document this new dependency. Now, the 1-npm folder right now has three things, the package.json file, the other package-lock.json file, and the node_modules folder. Now usually when you share your project with other developers, you don't share your node_modules folder. This is a big folder with code written by others. It does not belong to your team's get repo, for example. So what your team will get when you share this project is just the JSON file. So let me remove this node_modules folder to simulate that. So a team member just pulled this code, they now have the package.json file, and to build their version of node_modules, all they have to do is run the npm install command without any arguments, just like this. This command will install all the dependencies listed in package.json, along with their sub-dependencies. In fact, thanks to the package-lock.json file, they will get the exact same versions even for the sub-dependencies tree. For example, express depends on this bytes package here that was installed when we ran the npm-install command. Let's assume between the time that you added the express dependency and the time a team member pulled your code to use it, a new version of this bytes npm package was released. Your team member will not get that new version when they run npm install. They are going to get the exact same version that you used because of package-lock.json. So the version of bytes that was used here is 3.0.0. If you look at the content of this package-lock.json file, you'll see information not only about this project's direct dependencies, but rather the whole dependency tree for the project. Search for bytes, for example, you'll see how the exact version of the bytes package is included here, and you'll also understand how bytes was added to the project because it's a dependency of body-parser, which is a dependency of one of your top-level dependencies expressed in this case. While adding dependencies to package.json when you install them, you can also tell npm that a dependency is only a development dependency, which means it is not needed in a production environment. To do that, you run the same npm install command, but with a -D argument, D for development. For example, let me install the nodemon package with a -D argument here. You'll notice how npm added this nodemon package under a new section in package.json. This time it's devDependencies. This is where you should place things like your testing framework, your formatting tools, or anything else that you use only while developing your project. Now let's quickly take a look at the help page for the npm install command, and you do that using npm help install. In here, you'll see all the usage pattern and the options that you can use with the npm install command. And one of these options that you can see right here is the --production flag, or you can use the NODE_ENV environment variable and set that to production, and what that will do is it will completely ignore your devDependencies because these are development dependencies, so you don't need them in production. This is handy because this nodeman package is not something that you need in production. It's only use is in development to automatically restart Node whenever a file is saved in the project. Nodemon is one solution to the problem that you need to manually restart Node when you make changes to a file, and it's a good compromise in development environments. But it's totally not needed in a production environment. Before we move on to the next topic, which is this semantic versioning string that we've been seeing in package.json, let me show you a command you can use to automatically create a package.json file for a brand new project. So let me create another test folder here, I can use the make directory command here on this machine, and I'm going to call this my-project, and cd into it. So this is a completely empty directory. Now instead of manually creating a package.json file, you can run the npm init command. This command will ask you a few questions about your project and use your answers to create an initial package.json file for you. It tries to use defaults from your folder, for example, if your project is already a Git repo, it'll figure that out and include the repo URL. You can also run this command with --yes to just use the defaults instead of interactively answering questions. I'll do that for this example. Check it out. It created a package.json file with the name of this directory, an initial version, and this scripts section, which is an important one that we're going to talk about in a few videos, and it's a very good one. But first, let's talk about these little version strings, and understand the meanings of these elements.
Semantic Versioning (SemVer)
Npm uses semantic versioning, or SemVer for short, when it's time to update packages. Every package has a version. This is one of the required information about a package. That version in npm is written with the SemVer format. The SemVer string is basically a simple contract between a package author and the user's of that package. When that number gets bumped up to release a new version of the package, the SemVer communicates how big of a change to the package itself will this new release be. The first number, which is called the major number, is used to communicate that breaking changes happened in the release. Those are changes that will require users to change their own code to make it work with the new release. The second number, which is called the minor number, is used to communicate that new features were added to this release, but nothing major. All the changes are still backward compatible and it's safe for your users to install these minor releases and they will not require the users to change their code to make it work with these releases. The last number, which is called the patch number, is used to communicate that the release only contained bug fixes and security fixes, no new features and no breaking changes. You'll often see a few different special characters before the version strings in the package.json file. These special characters represent a range of acceptable versions and are put to use when you instruct npm to update your dependency tree. For example, the tilde character means that an update can install the most recent patch version. Remember, patch is the third number. For the version string on the screen here, it means npm can install any version that starts with 1.2 and is greater or equal to 1.2.3. So a 1.2.4 version would be okay and so would 1.2.5 or anything in that class, however, a 1.3.0 version will not be used. So basically this version range string is equivalent to 1.2.x where x is greater or equal to 3. On the other hand, a caret symbol in front of a SemVer string, is a more relaxed constraint. It will get the package updated to the most recent minor version. Remember, minor is the middle number. For example, caret 1.2.3 here will match any 1.x.y where x is greater than or equal to 2 and y can be anything. So you might get a 1.3.0 or a 1.4.5, for example, but npm will not update all the way to 2.0. I'll admit, this might be confusing at first, just remember that tilde is for safe patch level updates, whereas caret is for more relaxed minor level update. Npm has a site that may make understanding this a bit easier, it's hosted here at semver.npmjs.com. You can pick a package to see all of its available versions, then enter a range string to test. For lodash, for example, a tilde 4.16.3 matches all patch level updates that start with 4.16 and has the patch number greater than or equal to 3. While caret 4.16.3 will match what the tilde version matched, but it will also include all the 4.17 versions. You might find it easier to use the x notation, for example, your version range string can simply be 4.16.x, or even 4.x. The tilde and caret are helpful in communicating the exact version a range started with. I think SemVer is great and responsible npm developers should respect it when they release new versions of their code. But it's good to treat what it communicates as a promise rather than a guarantee because even a patch release might leak, breaking changes through its own dependencies. A minor version, for example, might introduce new elements that conflicts with elements you previously thought are okay to use. Testing your code is the only way to provide some form of guarantee that it's not broken.
Installing and Using NPM Packages
You have two options when it comes to installing an npm package. You can install it either locally or globally. When we worked under this 1-npm-command directory, all the three packages that we installed under this folder here were local. This is the default behavior of the npm install command. It'll just install the package locally under the project where you run the command. If the package is to be used within a Node folder, basically if you need to use it just for one project, you should probably install it locally. I'd say 99% of the packages that you use should be installed locally. The only exception is when you need to install global tools. For example, create-react-app is a package hosting a tool. React developers use that tool to, well, create a fully configured react application. This is an example of a package that's usually installed globally. You don't need to be in a specific directory to use this tool. You can use it anywhere. Also, once your generated react application is running, you are not really depending on the create-react-app tool itself anymore. You still depend on other packages related to create-react-app, but not on the package hosting the command. To install and update packages globally, you add the -g flag, which is short for global. Once a tool package is installed globally, its command will be available for you to run from anywhere. So for create-react-app, we npm install -g create-react-app. And once a tool package is installed globally, its command will be available for you to run from anywhere. The commands a package add to your system are listed right here when you install that package. So this create-react-app command is now a command that I can run from anywhere. Regardless of whether the package is installed globally or locally, once it's installed, you can require it from any Node script. Under this 2-usage folder here, we have a simple empty package.json file. Let's install the lodash package locally. Remember how to do that? Npm install lodash, and that's it, that will install lodash locally under the 2-usage folder, and place that package under the node_modules folder. Let's take a look under the node_modules folder, lodash is there. And note how this package has no sub-dependencies at all. The only package in our dependency tree right now is lodash. Now within the current folder, we can require the lodash package from any file. Here's a test that does exactly that. This test.js file requires the lodash library and then uses its sum function to sum the integers in an array and I'll output the result after that. Since lodash is already installed under the node_modules folder, this file will run fine and output the result. Now try to run the same file after deleting the node_modules folder. So rm -rf node_modules, all of it, and now you'll find out that if you try to run Node test.js, you'll find out that you can't run this file. Lodash is no longer available for Node to use. The node_modules folder is simply the connection between npm and Node. You can actually place the lodash dependency under the parent folder's node_modules folders. So the node_modules folder doesn't have to be in the exact directory where you are, Node will check all the parents for their own node_modules folder. So you can basically make a directory in your home directory called node_modules, just like that, and that directory would satisfy all the dependencies for all your projects, but really this is not a recommended approach. If your Node script requires lodash, like this, lodash should be a local dependency under the project's main node_modules folder, and the version you install should be documented in package.json so that all members on the team have a similar experience working the project dependencies.
Creating and Publishing an NPM Package
I think we're ready to bring out the big guns. Well actually, this is a lot easier than it sounds. Let's create and then publish an npm package. Try to do this exercise with me on your own. Pause the video as needed and mirror what I do. I've included a test file under the exercise files, right here under the 3-create-package directory. The goal is to make this file work and output what's expected, as you see here in the comment. So if we execute this file right now, it will not work because the frame-print package does not exist. This is the package that we will be creating. In this line, we're requiring this package and capturing the top level API of this package as the print variable. And then we're using the print variable as a function call. So the top level API in the package that we need to create should be a function. And here is the expected output. It just prints the message in the argument in a frame of stars. Alright, so let's start from a make directory command. Now usually you need to name the package as the string that we are using here to require it. So make directory frame-print. So under frame-print, we need to create a new file, let's call this file index.js. Now the name index.js is a bit special in Node and npm. By default, when you require a folder, Node will look for an index.js file under that folder, which will work in our case. So just to test this index.js, let's place a console.log statement here and just say Testing. So let me actually split this file in the editor here. Okay, so we've got this index.js file under the frame-print directory, and we have the index.js file that is under the test directory, which we're going to use to test the frame-print directory. Now to get this line here to work, this frame-print package should exist under the node_modules folder inside the test folder. We don't have that, we don't have a node_modules folder inside that folder. For testing purposes, instead of using this line, we can require the adjacent frame-print directory using relative paths. So instead of requiring this directly from node_modules, we can say let's go up one level and then require frame-print from the same level as the parent of this test folder where we're testing. With that, we can run this index.js file under the test folder and it will require the frame-print that we are testing right here. Alright, let's test that, so Node test/index.js, and you'll notice right away that we are in business. The console.log line is now showing up in our test. Very good. We're still seeing a problem that says print is not a function, because we did not export any API yet. So instead of console.log testing, let's go ahead and export an API. Now in a Node module, you can change the API using the exports special objects, so I can do something like a is 42 in here. However, if you need to change the top level export, you need to use the module.exports = notation. So our top level export here, which we are capturing in the print variable, is a function. So let's go ahead and do a function here. I'll name this function print. This function receives a single argument, so we'll call this argument msg. Now inside this function, let's console.log Testing from function, and let me go ahead and run the command again and make sure Testing from function appears. And it does. And you'll notice that the error about print not being a function is gone now, because our top level API is now a function. So now all we need to do is to make this function output the message with a star of frames. For that, we can use a few console.log statements. So we can do stars and we'll do another one, just like that, and inside the frame we'll just console.log the message, just like that. Let's go ahead and test, Node test. The output now matches what was expected here. Okay, so this is a very simple package and really the logic here doesn't matter. What matters is, we now need to make this package work without our modification to the test here, basically by doing that. And I'm going to assume that we're done developing this simple package. We need to publish this package and use it through npm. So what I want to do here, I'd like to go inside test and do something like npm install frame-print, and once this command is done, I should be able to run my test and see the exact same output that's expected. So, to get this npm install command to install our own frame-print, we need to publish that package. Now since the package name is unique at npmjs.com, to avoid conflict as we're all doing this exercise, I'm going to add the prefix to this package here and use my own username at npmjs.com. This way, when we npm install the package you can use your own username and you can publish your own package as well. So you actually need to create an account at npmjs.com if you don't have one. To publish any package at npm, you need to have credentials. So go ahead and do that. So once you have an account, you can use your username and password to publish the packages. So here's what you need to do in npm to accomplish that. I'm going clear this command. We'll come back to that in just a little bit, and now from anywhere here in your shell you need to do npm login. Npm login is going to connect your npmjs.com credentials with your local npm tool here so that you can publish to your account. So npm login will ask you for username and password. Go ahead and put these in and the email as well. This should match the email that you used when you created your account and now I am logged in to npmjs. Very good. Now we can publish the package. So this frame-print directory here is not yet a package because it does not have a package.json file. So we need to create a package.json file. Let me CD into it, frame-print, and to create a package.json we can simply use "npm init". The package name is no longer frame-print, so type in your npm username dash frame-print. The version can be 1.0.0, this is the first release. You can add descriptions, entry point test command, git repository, and I'm going to keep everything default here. Okay, we have a package.json, and guess what? Once you have a package.json, all you have to do is npm publish, just like that. This will publish your npm package to npmjs.com. So to test that the publish process was successful, you need to go to this URL: npmjs.com slash package slash the name of the package that you used and you should see your package published. This means that npm can now install this package on any machine. So if you haven't done the first half of this exercise, you can just install this package from npm and just use it. Now that we have a package published, we can go back to the test directory here, so I no longer need this index.js file. So focus on this index.js file, under the test directory here, it is using the name of the package that I just published, but remember that this package is still in npmjs.com itself. We need to bring it here locally using npm install for this file to access it. So the command that we need in this case is just npm install the name of the package, and this will download the package and place it under the newly created node_modules directory right here, as you can see. This is our package and it is exactly the code that we've written, but now this package was downloaded from npm, and once we have the package downloaded from npm we can go ahead and test the script as is and this script will just work. So this is really how simple it is to publish and use an npm package.
NPX and the NPM Run Scripts
Updating NPM Packages
In any Node project, it's usually a good idea to update your npm dependencies before testing each release to get the updates that have been made to your dependencies. You want to at least get any bug and security fixes in the patch number of these dependencies. It's also much easier to deal with these updates if you do them frequently. The npm update is the command you can use to do that. It respects all the semantic versioning strings that are in package.json. For this exercise, I have this 5-update directory here under npm and it has a package.json file with 2 exact dependencies, lodash and request. If you notice, these version strings for lodash and request do not have an caret or tilde prefixes. This makes them exact, they're equivalent to doing equal sign here, which means I am interested in exactly this version. So the first step is to install these dependencies. So we do that with npm install. This will download the exact versions that we're interested in and place them under the node_modules directory. You can verify what versions were installed using the npm ls command. Now the npm ls command is going to ls the whole dependency tree. So basically all your top-level dependencies, and all of their dependencies, and this request package has some sub-dependencies. But if you scroll up a little bit here, you should see to top-level dependencies right here at the beginning of the tree. And npm installed the exact versions specified in the package.json, which means, if I issue the npm update command, nothing is going to be updated because package.json asked for exactly these versions. Now by default, when you install any npm package, let's, for example, install the express package, when npm installs that, it writes it with the caret notation. So this was added to package.json, but it was added with the caret notation, which means that any update in the minor section of the version is okay. We just got the latest version of express, so npm update will also not do anything. We don't have anything that can be updated. We have the exact versions that we're interested in. Before playing with the version strings, let me remove this express dependency that I just installed. You do that with npm uninstall express. And this will remove it from under the node_modules directory and it will also remove it from package.json, so we no longer depend on this express package. To explore the update process, let's add a prefix here to the request package, I'm going to add a caret to the request package, so this is the default behavior when you install request, and for lodash, I am going to add a tilde. So the caret in request means that any minor update is okay. So anything in this section will be updated, while the tilde in lodash means only patch updates are okay. You can see all the versions that are available in an npm package using the command npm show, the name of the package, for example request, and this will show everything about request, if you're interested in just the versions you add the word versions. So npm show request versions. And this will give us an array of all the versions available in request. Now the current version that we have is here, and the latest version right now is 2.87.0. Because we used the caret in request, the next update process is going to take us from 2.85.0 to 2.87.0 because this is a minor update and that's okay with the caret notation. What about lodash? Let's check that out. So npm show lodash versions, and the latest version for lodash right now is 4.17.10 and what I have here is 4.16.2 with tilde notation, which means only patch updates are going to be applied. So we're not going to go all the way to 4.17, but, we can get 4.16.6 because that's a security and bug level fix. So we'll go from 4.16.2 to 4.16.6. Before running the npm update command, anyway, I like to verify what packages are going to be updated and to what exact versions are they going to be updated. And instead of doing this manually in your head and checking the versions, there is an npm command, called npm outdated. This will tell you what packages will be updated. It will not update the packages, but it will you that if you run the npm update, these wanted packages are what you're going to get. So the current packages are what you have. The wanted packages are what you're going to get if you run the npm update command, and in here it will also show you the absolute latest packages if you're interested in those. Because of semantic versioning, in here, we're not really getting the latest package, but the rather the latest bug fix that we can have. And again this is the absolute minimum thing you should do when you're planning to update your packages. However, just be careful and test because sometimes even patch-level updates might introduce new bugs in your system. Alright, so to update, we just run npm update and this will update the packages according to semantic versionings and once it's done, you can run npm ls (I'm going to pipe that on less), and this will give you the latest dependency tree, starting from your top-level dependencies here, that were updated to the latest according to semantic versioning. The package.json file was also updated to reflect the new versions that we're starting with right now. And note how the npm update command used the caret notation here, although I was using the tilde notation before, so be careful about that. Now what if I'm interested in an update that is beyond the version string? I can do that with npm install, the name of the package, and I can actually place any version here. So I can say 4.17, or just 4, and that will give me the latest on the 4. Or I can just say, latest, npm install lodash at latest, and this will give me the absolute latest lodash library, as you can see here, 4.17.10, and this matches the latest version for lodash, right here, with npm show lodash version, the singular version here is going to give me the latest version available for lodash, and it's the current version that I have in package.json, and this happened because I specified that I am interested in the latest version of lodash.
It's a wrap on this npm course module, we covered a lot of topics under npm, but they were really all important and will be needed when working with Node projects. I'd like to remind you thought that npm is just one package manager that you can use, there are a few others, most notably the yarn project by Facebook. The commands for these other package managers might be slightly different, but the concepts are similar. Npm is what you get by default when you install Node, but you can easily switch to other managers if you like them better. So in this module, we talked about what is npm and why is it needed. We explored the basic commands for working with npm. We discussed the purpose of package.json and package-lock.json. We learned about semantic versioning and how the npm commands work with them. We learned how to locally and globally install npm packages, and how to use these installed packages in Node. We created a simple npm package and published that on npmjs.com. We learned about using npx to execute locally installed executable npm commands. And we saw how to use npm run-scripts to have a standard way to run and test the tasks in a project. And finally, we learned how to check and update your project dependency tree. In the next module, we'll talk about the most important aspect about Node.js, which is how it works with slow operations without blocking other operations.
Modules and Concurrency
In this module, we're going to talk about two of the core fundamentals of Node. Its modules system, and how to define and require modules using the exports and require objects. And we'll also talk about how Node handles slow operations and allow the execution of many things at once without using any threads, which is a big deal. Let's start with the simple module system and talk about the three important keywords here, the module keyword, the exports keyword, and the require keyword.
Defining and Using Node Modules
Examples of Module APIs
Here are some examples that define and use multiple types of API objects. I am now under the 2-examples folder under the 4-modules folder, and in here we have 8 files, 4 different API exports and how to use them. The first file here is the simple case. When you want your API to be a simple object, you don't really need to use module.exports, you can just use exports, so you put exports and then you put properties on your API. And to use this module, when you call the require function on this module, you get back a simple API object so you can read the elements of the API as properties on that object, Node 1-use.js. We are getting the values that are exported through the API here because the API is just an object. The second example here, the top-level API is an array so I needed to use module.experts because I am reassigning the default export, which is usually an empty object, and now I'm saying my top level API is an array. To use this kind of API, under 2-use here .js, when you require the module, you get back the value that you need. That value itself becomes your API, and note how you can just inline the require call inside some other function because it's just a function call that returns a value. That value is your API and in this case it's an array. So node 2-use.js and you get the array directly here from the console log line. Your API can also be just a string. Look at this file. This API is returning a template string. Note that these characters are the back deck characters, not the single quote characters, right? Because this is a multi-line template string and the whole API is just returning this string. So if I want to use this API when I require the file, I get back an object which is a string, so if I console.log this object, it will be a string. So node 3-use.js. That is the string that this API is using. But let's say you do want an HTML template, but you want it to be dynamic. You want to tell your module that you'd like it to generate an HTML template for a certain title, for example, instead of a generic title. How do you do that? You do it by exporting a function. Check this one out, 4-function here. It's the exact same template text and it's being done through a template string as well, but the difference is that the top level API here is now a function and I used the shorthand notation for the arrow function here to have an arrow function that receives the title as a single argument and then return a template string that injects the title value right here inside the title tag. To use this API that is basically a function, when you require this function here, in 4-use, right, you get back a function. I called it here templateGenerator, it's a function, and you can invoke this function with any argument that you want because the top-level API is itself a function. I captured the result of the require call into a variable and I am now executing that variable because it's just a function and the result of executing this function is what the function returned here, which is the template. So this becomes my template and I can console.log that. So node 4-use.js and you get the exact same template, but now it is being customized with a custom title that I get to pass to the function that this API is exporting. So this API is now a lot more flexible than the previous one, which just hard-coded a value for me. So these are just a few examples of how flexible this module.exports object is and how you can use it to return any kind of API in a node module.
Node's Global Object
If defining a variable on the top-level scope in a node module does not make a global one, is there a way to define a global value in Node? The answer is yes, but you should really avoid doing that. The only global variables in Node are the variables attached to this special global object. This global object is the equivalent to the window object in browsers, and it's the only global concept in Node. If you inspect this global object, I'm using a .dir trick here to only show the top-level properties on this object rather than inspecting it as a tree, because it's a big one, if you execute this file, node 1-dir.js, you'll see a few things here on the global object. There is the process object, for example, and there are other features like buffers and timers. All of these here are globally available in all Node modules because they're attached of this special global object. So when you use a setTimeout call, setTimeout, you're actually using global.setTimeout, but because it's part of the global object, we can use it directly. We don't need to do global. because that's the concept of the global object in Node. Things will just be available globally. You can also attach new things to this global object yourself. In the 2-set.js file here I have a simple example. I am doing global.answer = 42, making answer a new global value now that you can access in ANY node module. Here's how you can test that, in the 2-test.js file here, I am requiring the file that added the global value, and then console logging answer directly. Not as part of an API. And guess what? If we execute 2-test here, because answer was set on the global object, you can access it from anywhere directly. Pretty Cool right? WRONG. Don't do that. Global values like this are bad. This is true here and it's also true in the browser environment. You should not make your code depend on any global state. Just treat this global object as a built-in thing that Node itself uses, and you shouldn't. Do not attach anything to it.
The Event Loop
Let's work under folder 4-event-loop. The first file here, 1-log.js has a single console.log line. Execute this simple file. Node 1-log.js line. What I want you to notice now is that Node has started an operating system process here and it's now done with that operating system process. Node has finished the process and the operating system terminated that process. A Node process, by default, will not keep running in the background unless it has a reason to. This 1-liner script here did not really give the Node process a reason to continue running. Let's now give the process a reason to continue running. We can do so by starting an interval timer. We've learned about timers in a previous module. Under the file 2-interval, there is a simple interval call here that executes a console.log line every 5 seconds. Go ahead and execute this file now, node 2-interval, and note how the Node process now did not exit. It is running and it will continue running forever until it gets crashed by an unexpected error, or the user kills it manually with CTRL+C. The real reason that process did not exist is that Node's Event Loop is itself busy now. What is this Event Loop thing, you ask? It is your best friend in Node. It's the hidden magic that will take care of anything asynchronous for you and you don't have to worry about working with threads. In other languages, if you need to do asynchronous work, you have to manage threads yourself, you have to start them, do the async work inside of them, monitor them, make sure they don't access shared data or if they do you want to make sure that there are no race conditions. It is a complicated task. Now some languages make it easier than others, but I think the big winner in that domain is Node because you just use Node's API and Mr. Event Loop here will do all the heavy lifting. The Event Loop is just a simple infinite loop that's built inside Node and its main task is to monitor any asynchronous operations that need to be run, and figure out when they're ready to be consumed. In this example, the Event Loop will monitor the setInterval timer, and every 5 seconds it'll take the interval's callback, which is the first argument to setInterval, the arrow function here, and it'll send this arrow function to V8, and V8 will execute what's inside that function. Because this is an every 5-second kind of thing, the Node process is going to continue to run forever, and it will not exit. So while this Node process is running, if you go to, in another terminal, and try the command ps -ef to to list all the running processes, and pipe the output of this command on grep node to just filter the list to any processes that are matching the word "Node", you'll see the process hosting our script, right here. You see that? It is still running. Now I have a few other processes that are matching "Node", I think these are actually the Atom editor itself here. Believe it or not, this Atom editor here is a Node application! It is a complicated one, but it simply run with Node using an application called Electron, which allows you to use web and Node technologies to build cross-platform desktop applications for Mac, Windows, and Linux. How cool is that. Okay, so our process here is the last one here because we started it last, so it will have the highest process ID. You can kill our continuously-running Node process using the Linux kill command itself and giving it the process ID here, but you can also kill any active Node process using Ctrl+C on its output here, and that action will stop the event loop and then remove the process from the operating system. Run the same ps -ef grep Node command now and you'll see that the process that was hosting the interval file is gone. Node's Event Loop works with multiple phases and queues. At this point, what I want you to remember is that EVERY Node process starts this infinite loop that we call the Event Loop, but when the process has no asynchronous operations to perform, the Event Loop will exit and the Operating System will terminate that Node process.
Errors vs. Exceptions
Do you know the difference between errors and exceptions? An error is simply a problem, so applications should not really catch that problem, they should just let it happen, while an exception is a condition, and applications usually catch that condition and do something with it. Let's talk about that in Node. In the 5-errors directory here, I have 3 files, the first file is 1-loop, and this file is simply looping over an array of files. Those are actual files that I have in my home directory, and they might be MAC-specific, so you'll want to change these files to something that you have in your local home directory. Then in this section, I am looping over this files array using a forEach call, and for each file, we are basically reading the content of the file. I use this path.resolve call to find the path of the file, and then, using the readFileSync method, which is probably the first time we're seeing this method, it's equivalent to readFile, but it will do the reading synchronously, not asynchronously. We're going to talk a little bit more about that in a future module, but for now, let's focus on thinking about what's going to happen if this code has an error, has a problem. So let's quickly run this code and make sure it doesn't have any problems as is. It is running, and it's reading 2 files data. Very good. Now, what if I injected a file here, that does not exist? So this file now, in the middle, does not exist in my home directory. What do you think the code is going to do? The code is going to try and read a file that does not exist, and Node will crash and exit. So after reading the first file successfully, for the second file, Node crashed and exited, so we're not reading the third file here. And this is normal. When Node executes an error like this one, which is basically not planned for, it will just crash. And this is important to remember here because although this process started the infinite event loop to do any async actions, it immediately stopped that loop when it encountered this unexpected error. And I say unexpected error here because the code did not account for the possibility of an error. We can make this code account for the error by simply making an exception for it. We write code to represent this exception. So let's assume that trying to read a file that does not exist is not really a problem for this code. We're going to upgrade this case from a problem, which is an error, into a condition, which is an exception. And the easiest way to do that is through a try/catch statement. So if you look at the second file under this folder here 2-try, you'll notice that this file is injecting the same not-found file, and it's exactly the same code except now, I've put the code inside a try/catch statement. So try this code, and then catch an error. And inside this catch block we can do whatever we want with this error object. Once you catch the error like this, the node process will not crash and exit when it encounters any errors within the catch block. When we run this file, Node 2-try.js, and because it is going to generate an error, but that error is wrapped inside a try/catch statement, the catch block will be executed. So note how the Node process did not crash and exit. We just got a file-not-found message for the second file, and then the Node process continued to read the third file, because the file-not- found is no longer a problem here, it's just an exception. It is okay to encounter files that do not exist, and just continue with your code. So this is a great feature, but it's also a dangerous one. This code means that we're okay with files not being found when we attempt to read them, but it also means that we're okay with any errors that happens when we read a file, not just the error about files not being found. If for example, the file is found, but has corrupt data, we'll treat that other error exactly the same, making the console.log line here not really an accurate one. Let me simulate that. The second argument for readFileSync accepts encoding string. So you can pass in utf-8 here to say I'd like to encode the content of this file when I read it with utf-8. Let's pass an invalid encoding label. If you execute the code now, because we did try/catch, and because we're generically saying file-not-found when you encounter an error, we're going to see the file-not-found message for all of them. But really the error that happened was not file-not-found, the error that's happening here is that the second argument here is an invalid encoding label. So this console.log message is no longer accurate. A more accurate message here would be "Something went wrong and we are going to ignore it" because this is just a generic catching of the error. And this is not good. Here's the thing, if you want your code to only catch the case when the file is not found, you should make this catch statement here a bit more specific. And in the third file here you're going to see an example of that. So take a look at 3-throw and you'll notice the exact same code, except we're now not generically catching the error message. We have an if statement. And this if statement is basically saying only ignore the error if it's a file not found error. Otherwise, throw the error object again, and this throw line is what Node would have done with the error if we did not have a try/catch statement, making this code only ignore a specific case and perform the default behavior otherwise. In Node, the default behavior for an error is to let the process exit because the state of that process is unknown and it's not safe to continue running with an unknown state. So let's try this one. Node 3-throw. This is fine because this is actually a file-not-found error, and now let's simulate a bad encoding string, run this process again, and the process will crash and exit, because this is a problem, this is an unknown error. It's a problem. And the safest to do for a problem is to just let the process crash and exit.
Okay, you might be wondering now, if it's a normal thing for a Node process to crash and exit and we should definitely let it do so. How is this going to be an acceptable thing in production? Remember when we talked about why Node is named Node? Because you don't run a single Node process in production. You run many. One working process existing should not mean the system is down. You should be running a cluster of Node processes. And that cluster will have a master process, which should be monitoring your application worker processes. This is true even if you're running a Node application on a single server. It's often the case where production servers will have many CPU cores. You should be running a Node process for each core. If you're not running a cluster in that case, you're not really utilizing the full power of your machine. And even if your single production server has a single core, you should be running a cluster anyway because that cluster has the simple job of monitoring the actual Node process and starting a new one when a process crashes and exists. You can run a simple cluster in Node using the built-in cluster module, and I go in depth about that topic in the Advanced Node.js course here at Pluralsight. Alternatively, you can use one of the many tools that wrap the cluster module to run your Node application in production. PM2 is one example of these tools. If you run your Node application through a tool like PM2, that tool can automatically use all the available cores in your server, and it will automatically create a new process every time an active process crashes and exits. PM2 can also reload your application without any downtime. A tool like PM2 is really a must in production.
Node’s Asynchronous Patterns
There is a library in Node called Event Emitter, and it's an important one. So let's talk about it here in this video. Under the 7-event-emitters folder here, there is an example.js for us to work an example on event emitting. And this is how we use the Event Emitter. We require the events library, which is built-in, we don't need to npm install anything here, and we usually name the result of requiring the event library, the EventEmitter class, just like this. The events library is one of the most important built-in libraries in Node because most of the other modules implement the Event Emitter module. For example, streams in Node are event emitters. We've seen a few streams when we talked about the process object, remember? event emitters as well, and in the next module, we'll work with more streams. So let's understand event emitters on their own first. After requiring the EventEmitter class, like this, we create an event emitter object, let me put it in a constant, name it myEmitter, and we use the new keyword on the EventEmitter class. So myEmitter is now an object that can emit events. An event emitting object has many methods, but here are the 2 most important ones. You can emit a string. This string is a name that can be anything and you use it to identify a certain event. So let's emit a TEST_EVENT string here. Now, the other method on any emitter object, is how you can subscribe to events emitted by this object, and you do that using the .on method. You say, myEmitter.on this TEST_EVENT string, and then you pass in a callback function in here as a second argument. The myEmitter object will invoke this callback function every time the event represented by this string is fired. So let me put a console.log line here and log something like TEST_EVENT was fired. Here's how this event emitting business is a lot more flexible than single callbacks. You can do this .on operation multiple times! This gives you the flexibility of defining different behaviors in different functions in response to a single event. Okay here is another one of them interview questions. If we invoke this file now, how many times will we see the "TEST_EVENT was fired message"? Think about it. And let me execute this file (node example.js) to test, and that console.log line was invoked ZERO times. The reason here is order. We subscribed to the TEST_EVENT three times, but that TEST_EVENT was not fired after we subscribed to it. It was emitted once BEFORE we subscribed, but no one was listening at that point. If you emit the event after subscribing to it, you should now see the three callbacks getting executed. Here is another interview question. If I don't emit this event after the subscription that I just did, and I just keep this line here, what can I do to this line to make it trigger the subscribers callbacks that happened after it, without moving it to after the subscribe calls? I can use the Event Loop. I can delay the execution of this line to the next tick of the event loop by using a simple setImmediate call, for example. Here's how to do that. If we wrap this emit line with a setImmediate call, the callback of setImmediate will be placed on the event loop, and it'll be invoked after the rest of this program is executed. So these three subscribe calls will happen before the emit call in that case, and we see the "TEST_EVENT was fired" message. This is event emitting. Very simple yet very powerful because it allows for modules to work together without depending on any APIs.
This was an important module in the course. We talked about how to define and use modules in Node and we've seen some examples to define and use multiple types of APIs. We talked about the special global object and how you should avoid using it. We then talked about Node's Event Loop, the hidden magic that allows you to easily do asynchronous programming in Node without using any threads. We briefly talked about error handling and how to make exceptions for problems. We talked about the concept of Clusters in Node and how a master process can restart other workers when they have problems. We looked at Node's asynchronous pattern and saw how the promise pattern is a lot more flexible and readable than the callback one. And finally, we went through a simple example for the Event Emitter module, which is an important one because most other modules in Node use the Event Emitter module one way or another. In the next module of this course, we'll talk about Web servers and how Node provides some utilities to program these type of servers.
Working with Web Servers
Hello World... The Node’s Version
Monitoring Files for Changes
Node.js, in development, is a bit different than many other runtimes and frameworks. It does not auto-reload any changes. You're going to have to remember to restart it, but let me show you a better option. Remember in this same example, when we changed things here and saved, we needed to restart Node to see the new changes in the browser? This is not ideal. The popular solution to this problem in Node is to monitor the operating system files for saving event, and try to auto restart Node when you get these events. There are many npm packages that are designed for this exact purpose. I usually use nodemon. You just npm i -g nodemon, it's probably a good idea to have this package installed globally, but it doesn't really have to be. nce you have access to the nodemon command, you run your code with nodemon instead of node. So nodemon 1-hello-world.js. The nodemon command is a wrapper around the node command, so the nodemon command we just typed will run our server as if we're running it with the node command, but now it will monitor the files for any save events and reload itself when files are saved. So when we change this to "Node" and just hit the save button, nodemon automatically restarted itself, and you'll be able to see the new changes in effect. Of course you don't need this nodemon package in production. This is just a development convenience.
The “req” and “res” Objects
The requestListener function receives these 2 arguments. Let's talk about them a bit more. How to use them. What information and capabilities do they provide? How to know what to do with them. The easiest way to explore them is to log them. For example, let's console.log the req object here. Now to get this line to execute, we don't need to restart Node, thanks to nodemon, but we need to go make an HTTP request, because this is the function that gets executed per HTTP request. When we do a request, you'll see this big object here in the logs. This is the request object and this is everything you can do on it. It has a lot of properties and the printing here is also printing everything nested under the main properties of the request object, which is a bit hard to read. To get a smaller output about this request object, use dir instead of log, and pass in, as a second argument to it, the object with a property depth equal to 0. This means do not print any nested objects. Only print the first level of properties for this req object. Node has restarted. Refresh to see the new output, and now, we see only the first level of properties and any nested objects will not be printed here. Note a couple of things about this output here. The request object is of type IncomingMessage. It's the class that was used to internally instantiate a request object. This is good to know because if you want to find the documentation about what you can do with the request object, you need to go the IncomingMessage class under the HTTP documentation. So that little request object here belongs to the IncomingMessage class. All the properties and events you find in this documentation section applies to the request object. Because the popular name of this incoming message object is "request", one might confuse it with this ClientRequest class. This ClientRequest class is used when you want to use the http library as an agent to fetch information from an http server, rather than as a server, which is what our example is doing. So remember, the request object within an HTTP server listener function is of type IncomingMessage. The other important thing to notice here is that the console.dir line was executed twice. We have 2 request objects here, which means for a single HTTP request coming from my Chrome browser, the listener function is executed twice, not once. If you want to investigate this a little bit further, and you should, drill down for more clues. For example, look at the request property values here. In particular, the url property. So instead of console.dir request here, let's just log req.url and see what that will output. So server restarted, refresh this guy, and take a look at the two URLs we're getting here. The root request, and the other is for a favicon request. My Chrome is automatically trying to ask the server if it has a favicon. This url is a property on the req object and it's actually the property that Web frameworks use to implement their routing features. We'll talk about web frameworks in the next video. Let me now console.dir the response object and take a look at that. Refresh the browser. And the response object is of type ServerResponse if you need to look it up in the documentation. We can use this response object to send a few things over to the requester. It can control things like the status code and the status message, the headers of the response, and any data we'd like to include in the response body, which is what our example is doing here. Both the request and response object are streams here. The request object is a readable stream, while the response object is a writeable one. Because streams are all event emitters, we can subscribe to events emitted by these objects too. We can also use them with other streams, which is a really good thing for you to learn right after this course. I cover it in details in the advanced node course at Pluralsight.
Node Web Frameworks
Using Template Languages
Working with the Operating System
One of the common tasks in any backend program is to work with the operation system resources. Read information from the OS and write information to it as well. Node has a few built-in modules that provide some core features around these tasks. There is the OS module for general communication with the OS, and the FS module, which is specific to reading and writing to the OS file system. And there is also the child_processes module that enables you to run any operating system command from within Node. Let's talk about all these three handy modules.
The os Module
The fs Module
The fs module provides a big API for interacting with the file system. We've seen a few of its methods already. You've seen the basic readFile and writeFile methods, but there are so many other methods under this module. This is probably the biggest API among all the built-in Node.js modules. Using the fs module, not only you can read and write files as buffers, you can also work files as streams, which is a lot more efficient when working with big files. You can also work with directories and do many operations on both directories and files. Let me show you a few examples of things you can do using the fs module. I am using the promise-based API for these examples. You can also use the original callback API for them, and most of the API methods here have synchronous versions that you can use as well. To use all of these promise-based methods, you just await on them within a function labeled async. Most of these methods are based on a filePath and some of them take an optional configuration object. These square brackets here indicate that this options object is optional. If you do want to read and write files, I recommend looking into the createReadStream and createWriteStream methods. These are so much better than readFile and writeFile, because the regular readFile and writeFile use buffers to work and will use a lot more memory than the streaming-based ones. Using the fs module you can append data to a file, this will also create the file if it does not exist. You can copy files, and if you attempt to copy a file to a destination that already exists, it'll be overwritten. You can read information about files using the stat method. Using this method, you can get data about the file, like its size, for example, without needing to read the whole contents of the file, which is nice. This method will also include time-related data about the file, like when was it created and when was it last modified. You can read user's permissions for files and directories using the access method and you can change the permissions and even the owner of a file with the chmod and chown methods. You can link and unlink files and you can even truncate a file's content if you need to. You can make new directories, read a directory list of files, remove a directory, and rename directories and files as well. This is a very powerful module even if you do not use Node as the host of your backend servers. For example, I recently used the fs module to generate test files for a project that had hundreds of UI components. It took me about 10 minutes to do that using the powerful methods you can see here for the fs module.
The child_process Module
The child process module provides four main methods that allow you to execute any operating system command from within a Node process using a sub process and then get the result of running that command in your main process. The sky's the limit here. Anything you can do in your operating system shell can be done from within Node. The four main methods are spawn, exec, execFile, and fork. Fork is a special one to create sub processes that run node itself again. This is the concept the powers the cluster concept in Node. The other three can be used to execute regular OS commands like ls or pwd, for example. These have a few pros and cons here, but I am going to save you some research here and tell you that you should use the spawn method. Here are a few examples of things you can do with spawn. You destructure the spawn method out of the require call to child_processes. Then you call the spawn with a string representing the command, pwd here, for example. Once you get the result of that, you can use the pipe method to send its output to the process stdout, which will print the spawned process output. In this second example, I am reading the content of a file under my home directory using the cat command. If you need to pass the command arguments, you can use the second argument of spawn, which is an array of all the arguments you wish to pass to the spawned process. Here's an example sending two arguments. This will list all the files in the current directory. And finally, if you need to use shell syntax in your spawned process, you can pass in a configuration object with shell set to true here, and with that, you can get all the power of the shell. I am using the tilde here and the pipe operator, and passing arguments to commands directly. This is a powerful option, but it's also a dangerous one, especially if you do not trust the source of where this string is coming from. So only use this shell option if you need to and if you have control over, or trust the source of the commands.
Debugging Node Applications
Before we wrap up this course, I'd like to show you one handy trick about debugging Node applications because I know you're going to run into problems. Node comes with a built-in debugging client, but that one is limited. The cool thing about Node's debugger is that it's beautifully integrated with Chrome Dev Tools! If you're familiar with these, you know how powerful they are. All that power is available to you when working with Node as well. To demonstrate this, I've prepared a file with a bug under the 6-os folder here. File 4-bug has a simple function that is supposed to take an array and convert into an object. So here is some test input, this is an array, and you can see the output that I am expecting here. It's supposed to convert the array into an object. You can easily convert an array into anything using the powerful reduce function. However, when we run this file, node 4-bug, we don't get the expected output, but rather a weird one. So what is going on here? We can debug! Simply run this file with --inspect-brk, brk here means break, so don't execute anything at first but rather stop in a debugging state. When you run this, it starts the debugging utility. It doesn't run the file. Now in your Chrome, go to this special URL: chome://inspect. You'll see your Node process listed here inside targets, and it is inspecting our file. Click on this inspect link. And just like that, you have the powerful Chrome dev tools working for your Node script. How cool is that? This is really a big deal. Go ahead and try it out and see how fast you can find bugs now. You can place and clear breakpoints, you can set watchers, you can step-over, step-into function calls, you can use the console to see values and structures, and many other powerful features, really. And before we debug this code, note how, the wrapping function showed up here, because every Node module gets this wrapper. You don't just execute your own code. You execute your code within a function. So this is a good reminder here, right? All right, so let's debug this code. Let's place a breakpoint right here on line 3, which is inside the reduce function. My expectation here is that the line should be called five times, and each time it should have an accumulated version of the desired object. And now, click the resume button here and it should stop on the first call of this line. And it did. Now you can hover over the variables here to see their values, or you can just use the console.log to see the values as well. So it looks like the current is an empty object, and the accumulator is not an empty object. So this right there is not matching my expectations. The accumulator should be an empty object at first, and the current should point to the current element that we're iterating over. So right away you realize that the current and the accumulator are swapped. The first argument is the accumulator, and the second argument is the current element. So to fix this bug, all we need to do is make the accumulator first, and the current second, and test to see if that works. And it did.
Course Wrap Up
Samer Buna is a polyglot coder with years of practical experience in designing, implementing, and testing software, including web and mobile applications development, API design, functional...
Released11 Sep 2018