Guide For NodeJS - the Hard Parts
1. Introduction
Key Node.js Features
Some of the critical features of Node.js include:
- 1 Easy: With tons of tutorials and a large community, Node.js is relatively easy to start with — it’s a go-to choice for web development beginners.
- 2 Scalable: Node.js is single-threaded, which means it can handle a massive number of simultaneous connections with high throughput and provides vast scalability for applications.
- 3 Speed: Non-blocking thread execution makes Node.js fast and efficient.
- 4 Packages: A vast set of open source Node.js packages is available that can simplify your work. There are more than one million packages in the NPM ecosystem today.
- 5 Strong backend: Node.js is written in C and C++, making it faster for running a server and adding features like networking support.
- 6 Multi-platform: Cross-platform support allows you to create websites for SaaS products, desktop apps, and even mobile apps.
- 7 Maintainable: Node.js is an easy choice for developers since both the frontend and backend can use JavaScript.
Node JS is one of the most powerful technologies to emerge within the last 15 years. It allows us to build applications that enable us to handle millions of users at once. Some of the largest companies in the world, therefore, use it:
- Uber
- Netflix
- IBM All of them use Node. But not only that! Node if going to allow us to build Desktop apps, compatible with windows, Mac and linux operating systems. Here are some softwares that use Node (packaged up as Electron):
- Slack
- Twitch
- Vs Code
- Atom But most important for us full stack developers, it allows us to build apps end-to-end in just one language - which is JavaScript. This concept was introduced as - Isomorphic JavaScript, which means, writing client-side code & server-side code in the same language. Up until some time ago, before Node came to be, the most popular languages to write server code were:
- PHP
- Java
- Ruby
- C/C++ And as you can see, JavaScript ain't one of them. JavaScript was a Client side language for so many years. It doesn't have access to our computer's internal features. Our dream, for many years, was to be able to use JavaScript for accessing all our computer's internal features. For instance, the networking ability, the ability to receive a message, to look at a received message, inspect it, and decide what to send back to the client. Well, actually, there are a bunch of internal features of our computer we might want to use, such as:
- Network socket - Receive and send back messages over the internet
- Filesystem - that's where the html/css/js files are stored in files
- CPU - for cryptography and optimizing hashing passwords
- Kernel - I/O management
2. The Process Object
The process object in Node.js is a global object that can be accessed inside any module without requiring it. There are very few global objects or properties provided in Node.js and process is one of them. It is an essential component in the Node.js ecosystem as it provides various information sets about the runtime of a program. This process object is an instance of the EventEmitter class. It does contain its own pre-defined events such as exit which can be used to know when a program in Node.js has completed its execution. Process also provides various properties to interact with. Some of them can be used in a Node application to provide a gateway to communicate between the Node application and any command line interface. This is very useful if you are building a command line application or utility using Node.js. • process.stdin: a readable stream • process.stdout: a writable stream • process.stderr: a wriatable stream to recognize errors Using argv you can always access arguments that are passed in a command line. argv is an array which has the running program as the first element (which is always going to be Node) and the absolute full path of the file as the second element. From the third element onwards it can have as many arguments as you want.
- process operation 1: "on" or "addListener"
addListener and on are actually aliases.
Doing process.on('some-event', cb)
is the same as doing process.addListener('some-event', cb)
.
Events Related to process:
• Event 1: 'exit'
Run the below program and you can observe that the result comes up with status code 0. In Node.js (and in any other programming language) this status code means that a program has run successfully.
process.on('exit', code => {
setTimeout(() => console.log('Will not get displayed'), 0);
console.log('Exited with status code:', code);
});
console.log('Execution Completed');
• Event 2: 'unhandledRejection'
Run the following code:
process.on('exit', code => {})
• Event 3: 'unhandledException'
Run the following code:
process.on('exit', code => {})
-
process operation 2: Read / Write You have Read / Write operations just like console.log on the process object. process.stdout.write('Hello World!' + '\n');
-
process operation 3: exit Terminate a program, and giving it an exit code. process.exit(1);
-
process operation 999: other useful things process.pid process.argv process.env process.cwd() process.memoryUsage()
3. The Mental Model
- Background
In this guide, we're going to learn what it means to "open a web application". We're basically, in the most fundamental way, talking about communication between 2 computers. Just to have a clear distinction from now on, let's call computers who initiate the communication Clients, and say that we have many of those, and call the computer which holds the application the Server, which is called like that because it serves something back. A "web application", is, at it core, an application running on a server (computer) that can receive messages from... other computer, clients. Th "web application" is (or at least should be) always "on", always connected to the internet, and always ready... to receive messages sent out by other people's computers. And what do I do as a developer to tell the computer to look at the message and send something back? I write code!
But there's a problem...
Where does this message, the one being sent to me, arrive on? It arrives on the Network Card.
In what languages can I write that has access to my network card?
- PHP
- Java
- Ruby
- C/C++
So we've established that a language who wants to write server code, needs to have access to the Network Card.
Also, this language will need to have access to files!
Because eventually, where do we write code? In files!
So this language must have the ability to read from a file, and maybe even write to a file.
It needs to have access to that computer's filesystem. We put the Network Card and the FileSystem under a group that from now on we would call Internal Features. JavaScript doesn't have access to our computer's Internal Features. What language does have the ability to access our computer's Internal Features? C++
JavaScript is gonna have to work in hand with C++, so that we could write JavaScript code to control C++ built-in features that allow us to control our computer's internal features. And these two together are known as NodeJS. Why is it called Node JS when they're so much C++ in it still baffles me, but nevertheless - it is known as Node.
The model we are going to see over and over and over, again and again repeatedly, is: JavaScript affects Node (which is C++), which affect a computer's internal feature. We're going to spend the rest of this following course in writing JavaScript, to control indirectly via C++ the computer features we need to have access get our inbound message, and then send back a response (the right data, or the right html file).
Does that mean I need to know C++?
It turns out that - no.
We're going to get from JavaScript a TON of labels, built in to JavaScript that are gonna give us control. labels like "http", "fs", and not so many more, that are not built-in per-say, but you'd have to sort of summon on-demand (import). And those labels will give us access to C++ features, that give us access to the computer's internals. We don't need to know C++ code to do so, but we do need to understand the "Mental Models" of how it's working, and how these JavaScript labels are gonna trigger C++ features. That's what we're gonna do. We'll get an intimate understanding of how JavaScript labels take command through C++, and get a ton of help from Node C++ (much of Node is a C++ code) in order to take command of the internal features of our computer.
- Our Code Mental Model
We write JavaScript code, or more accurately - we use JavaScript label that Node had prepared for us, in order to trigger Node C++ code, that eventually reaches an Internal Feature. These JavaScript label look like they're JavaScrip functions, but no, they are in fact facades, for in reality they are commands to Node C++ features. The vast majority of all the interesting stuff, of Node's hard work, is happening down over at C++ world, so we better understand what's going on there, and also we better understand what's going over at our computer's internal features - like the socket. But first in foremost, we had better have a good understanding about JavaScript. Important note! C++ isn't gonna go directly to the network card itself. It's actually gonna interact with some abstraction layers of the operating system. Things like: E.Po, K.Q. we'll learn all about those stuff later on so hold on on that for now. In our mental model, JavaScript is going to have 3 major parts:
- Save data - numbers, strings, arrays, objects. And also - functionality, which means code that's gonna run later on.
- Runs code on data. Run a functionality on a piece of data. Run a function (function = a saved code that has not been used yet).
- Has a TON of built-in labels that are gonna trigger Node features, that are written in C++, to use our computers internals.
JavaScript's main data store is called "the Global Memory". The store of data I known as the "global memory". JavaScript has the ability to go line by line, and that's called the "Thread of Execution". We mentioned earlier that a "web application" is an application able to receive messages from outside users/clients, and that we use code to look at those messages.
So first,
How do we bundle up code? We wrap it in a function! So although it may seem trivial, functions will turn out to be the most important construct in JavaScript. Functions = code that's bundled up which we're saving to run at a later time. In simple JavaScript, in order to run a function, we, ourselves, the developers, put on the parenthesis at the end of a function's label manually. I have a sneaking suspicion... that Node might end up being the one who puts the parens on the end of our function code. And also, the one who inserts the input automatically for us. And that's gonna turn out to be the entire paradigm... of Node.
Because, when a request comes in, I don't know when it's gonna come! Node would be the one to know, so therefore Node must be the one to trigger... executing the function.
4. The http module
(HTTP = HyperText Transfer Protocol) Let's remind ourselves the final goal first. Our dream is to write JavaScript code, that can look at an incoming message off the internet, inspect it, and send back the right response. That's our dream. So, there's better be a label in JavaScript, that accesses, or sets up a Node C++ feature, that can access the networking feature of our computer. There better be one! Well actually, there is - it's called "Net". But there's a more specialized one, called "http". "http" = a Node feature that's gonna access the network card (effectively) and be able to receive messages in the HTTP format. Later on we're gonna see HTTP in a greater detail, but for now know that it's a format by which you send messages (or "requests") from a web browser, and we need our network open, and we're gonna discover that what we actually open is a socket, which is an open connection to the internet, an open channel. A two-way open channel. And we're gonna discover that this open connection, we need to have it formatted such that it ready to receive HTTP formatted messages. How are we gonna do that from JavaScript? Via labels, that trigger a Node C++ feature, that triggers an internal feature of our computer. A powerful feature of Node C++ is "http". We are now going to see an "http" feature of Node being used to set up and open a socket connection to the internet. Socket is a posh word for saying "an open channel for data to go in and out of a place over the internet". Sounds complicated right?? All we ever wanted was to build a cool looking app! But folks, if we get this principle down... we'll discover there's nothing else to Node. In this guide we're going to see ALL features of Node, besides one - Multiple threaded tasks. Multiple threaded tasks meaning, that in some way we could handle more than one, multiple JavaScript instances at a time.
5. createServer
It turns out, that http has a built-in method called createServer. createServer can also be referred to as a label... for a Node C++ feature, that sets up an open channel to the internet. http.createServer is a command for a Node C++ feature. To do what? To set up a network feature of Node, specializing in http protocol, ready to receive messages. Well, that's not really interesting, cause what's really interesting is what it's gonna do in the computer's internals. With the help of libuv. libuv is a bunch of pre-written C++ code, technically built separately from Node, BUT! Its most prominent use is in Node. libuv is a bunch of C++ code written to ensure that we can run Node on any operating system, and link up effectively between c++ code written in Node, with any computer internal structure, whether it's a Mac, linux, or windows. So, let's repeat that one more time: http.createServer is a command for a Node C++ feature, that with the help of libuv, is going to set up in the computer's internals an open socket, an open channel to the internet, ready to receive messages. That's it! One line! const server = http.createServer(); Our computer is now ready (almost) to receive messages. In one line! In one label! It opened that channel. In and out messages. One issue though. There are about 64,000 numbers that represent entry points to my computer. That's a bit of an issue, because when a message arrives at my computer, which entry point it would come in at? The default port for ANY http sent message coming from a browser is (of course) 80. So that message is gonna try and arrive at port 80. Well, One might say "Damn, I already created a server with that one line, without setting the port number". "How the hell do we continue to edit it? It is even possible?". Luckily, Node realized that we're not gonna do all of the commands for the underlying C++ feature in one line, so what does it does? The other thing that createServer dies is immediately, and this is crucial to understand, IN JAVASCRIPT returns out an object full of functions, methods, including ones like "listen" and "on", all of which, when run, will allow us to continue edit the instance of the http feature in Node that we've set up. Let's repeat that one more time: What http.createServer does in Node is divided into 2 parts. One part is related to what it's doing in Node, and the second part relates to what it's doing in JavaScript. In Node, createServer sets up the "http" feature of Node, which is actually behind the scenes sending a message to the computer's internals, where it's going to turn on in the networking portion of our computer an open socket, which is a fancy word for saying "an open channel to the internet that is two-way", meaning that it can receive data and send data back. Node's output of running createServer is setting up a socket. In JavaScript, createServer returns an object full of methods, which we call "edit functions", since they let us "edit" this particular Node HTTP instance, that are linked directly to the particular socket that has been opened by createServer. This object being returned is a JavaScript output of running createServer, which allow us to ongoingly modify the server. Here are some of the main functions that are available to us on the return object:
- listen
- on We've also mentioned that a socket needs a port number specified. The listen method is an edit function that lets us edit the port number of the http server instance. The "on" method (function) let's us setup what functions we want to auto-run when a certain event occurs.
6. The "listen" method
The "listen" function is a method on the returned JavaScript object which comes back from createServer. The "listen" method is used to have the HTTP server start listening for connections. The "listen" method has an edit access to the open socket created by createServer. The "listen" method doesn't do anything in JavaScript. That thing that the "listen" method can edit is the port number. It sets the port to whatever you tell it to. We use it like so: server.listen(80); And there we have it, our computer is now ready to receive messages from the internet, in two lines!! A lot of shot is happening behind the scenes, but... it's 2 lines in JavaScript, to trigger a ton of sophisticated stuff, like opening a channel at a specific entry point.
7. Auto-Run a Function
- Auto-Run A Function
const server = http.createServer(); server.listen(80); A recap from earlier - we set up a channel ready to receive data in 2 lines. So, let's first review a scenario: A message comes into our computer, an inbound message comes, and we want to do something like this: Pseudo-code: if (inboundMsg) --> send back proper response There's a problem here. When is this code going to run? In fact, I have no idea! A message could arrive at any giver time day or night! Who does know when an inbound message arrives? Node knows! And so, perhaps... we're going to rely on Node to AUTOMATICALLY run this line of code for us. But how could we bundle up the code in order for it to be triggered to auto-run by Node? In a function. And that's what we're gonna do again and again and again. We're gonna bundle up code in a function, that we want to have auto-run by Node to do stuff like - look at an inbound message, and send data back, when Node sees (with the help of libuv) that a message has arrived. We are going to save code, wrap it in a function, give that function a name like doOnIncoming, give that function to Node. And in return, Node is gonna auto-run that function for us, when a request (inbound messages / request for data) arrives from a user. const server = http.createServer(doOnIncoming); server.listen(80); It turns out, createServer does an extra thing! It accepts a function as an argument, that would be auto-run by Node on an incoming message. Whatever function we insert there, is what's going to be auto-run when a message comes in. Keep in mind, As we're going to see this again and again, Any task that will take a long time, will be set up in Node, and then have a function attached to it, that will be automatically triggered to run when the background task either completes, or has activity. Among these tasks are: talking to a database, talking to the file system.
- Auto-Insert Arguments
We saw that createServer takes in a function as its first argument, and that we gave it doOnIncoming. When relying on Node to auto-run our doOnIncoming function, we give it the function's label. Node will then automatically add parenthesis in order to execute my function when the time comes. However, that creates a problem for me, because if I'm not the one putting the parenthesis, how am I able to pass in arguments? to insert the arguments? It turns out, Node has two main jobs. Automatically add the parenthesis when the times comes, in order to auto-run it, and also... automatically insert the arguments full of data for me. And wouldn't that be amazing, if that data were exactly the inbound message that I need to inspect, in order to determine what to send back. All I have to do in return, is prepare my function in a such a way, that I could catch those arguments when they are auto-inserted. And that's all possible, by using placeholders known as - parameters.
8. Request & Response
When an inbound message arrives at my computer, Node takes the function that we told it to run (my doInIncoming), and it runs it. But it also... passes in 2 objects. Now, we said that we want to see the message, and be able to read it. Wouldn't it be amazing, if that first argument passed is actually the message itself? Turns out, it is! Do I get the the inbound message as a string? I actually don't. Because Node wants to make my life easier. So instead, the next thing Node does as soon as a message comes in, it gonna immediately going to set an http message, ready to send back, but both of them are in a format that I don't wanna deal with in JavaScript. So instead, Node is going to automatically package up 2 JavaScript objects for us. Note that they are JAVASCRIPT objects, but they are being set by Node. Node is going to auto-create them. These two object are the most important objects in all of Node. The first one is gonna package for us in a nice JavaScript object the important information from the inbound message.
What's the most important information we got from the message?
- URL
- headers
- method
- . . .
Node is gonna parse the message, grab all the thing you might want and need, and put them in the object above, in those nice little objects (like req.url). This object comes without a label, so we need to give it one, we need to put placeholders (known as parameters) to capture this object and give it a name. The name can be anything we want. Traditionally, we called it "req", but you can choose your own. Now, what do we want doInIncoming to do eventually? Reading is not our main purpose, it's not our goal. Our goal is to eventually send back a response message! Reading is just means to that goal! How are we gonna do that? We better have inside this function's code access to this message that's going back, so we can add stuff to it, some data, maybe some html, or css, and send it back. So the next thing Node does, as soon as a message comes in, is it's gonna immediately gonna set an http message, ready to send back, but both of them (the incoming & the created outgoing) are in a format that I don't wanna deal with in JavaScript. The second object Node created for me has a bunch of methods, javascript labels, for editing/updating the final result of the outgoing message. Fundamentally 2 different behaving objects. One has actually got the inbound data on it, which we can access, the other has functions that when we run them from javascript as labels, back into Node, to add stuff to the outgoing message which gets sent back.
So, a full recap:
In 3 lines, we have set up our server.
function doOnIncoming(req, res){ res.end('Welcome to LuckyLove!') }
const server = http.createServer(doOnIncoming);
server.listen(80);
What does this code do: ⁃ save the function ⁃ use a label called createServer ⁃ To set up a Node background feature, which really is an internal feature, which is specifically opening a socket, an open channel to the internet. The talking to the internal features part is being done through a library called "libuv". ⁃ To store a function (doOnInbound) to run when an inbound message comes in, by passing it as the first parameter. A function that will be automatically triggered by Node, and.... the most important piece of all, on an inbound message, not only Node is going to auto-run the function, Node is also going to auto-insert the 2 most important objects of all to it, the request & response objects, that were automatically made by Node. One of them has all the information from the inbound message, packaged up in a nice format, each property holds 1 piece of information related to the message. And the second one is an object full of functions all of which are linked to auto-created response message, that we can add text to, or content to, HTML files images JavaScript files, by running some of the function on that auto-inserted second object. One of them is res.end, which tells Node "Hey, this message is ready to be sent back, let's go!". End can accept some things ⁃ to get an object full of functions, that we call our "edit functions", that when run they tap into this instance of internal feature, and update it on the go. ⁃ Use the "listen" method on that returned server object to update the port to 80, and part listening.
9. Editing the soon-to-be-sent-out response
As we've mentioned before, Node gives us an object full of edit functions, which we can then use to edit our outgoing response. The most common ones are "write" & "end", but we will see some more today. The list is:
- end
- write
- headers
10. How to turn on Node
Where do I write JavaScript? I can write JavaScript anywhere, it's just some text. Typically I write JavaScript in a file, and then give it to the web browser to execute it, but I can write it anywhere. Node, however, is an app. It's just an app that I turn on, just like a web browser. A web browser is just an app that has access to my computer's internals, because we know it can send messages to the internet. Node is no different. How do I open a normal app on my computer? Double click. Unfortunately Node isn't like that. Because developers hate double-clicking. They are pathologically oppose to using their mouse. So, instead we have a different way of of turning on apps, of interfacing with the computer's features. And that's by using the terminal/command line. We can even turn on VS Code from the terminal. And we can also turn on Node, by writing "node", and pressing Enter. When we do so, we want it to start running some JavaScript code. Where do we tell it to run the code from? Aha! We're gonna save some code in a file, and then give the name of the file along with its path to the node command:
node ./server.js
If Node is installed on your computer, it will start loading. It will turn on Node, which would turn on the JavaScript engine, that will allow you to turn on node features by writing javascript (-ish) code. We write all that javascript code in that saved file which path we provided. Before the days of "nodemon",iIf we had done any changes in our javascript file (server.js), we would have had to turn off Node, turn it back on, run through all the javascript code, and set up all of those internal features all over again.
11. Error Handling - The "server.on" method
We get errors in server side development. We're bound to! Because eventually we're dealing with someone else's computer! Trying to send us messages. There're a 1000 things that could go wrong in the process. We need to be able to handle errors. We need to understand better how our background http feature is working. Right now, it only auto-triggers doOnIncoming, when the message comes in. But what if we get a corrupt request? Do we want to look at at it? Investigate it? And send something back? No. We wanna look at it, maybe log it, and see what error is at hand. Wouldn't it be nice if we could set up another function that will be the one to run when a client error shows up? Turns out, there's a little piece of Node we haven't discussed. When the inbound message arrives, it's not automatically running the function doOnIncoming. It actually sends out a loud shout! Within Node. A message, they call it an "event", just a word/string, that is emitted in Node. And that word is "request". That event is what triggers the call to (the execution of) doOnIncoming. How do we tell Node that we want THAT function to trigger on THAT word when it's broadcasted? Well, we actually did that implicitly, here:
const server = http.createServer(doOnIncoming);
The first parameter passed to createServer, is actually saying "Hey Node, when the word/event request
gets emitted, run doOnIncoming".
But we can actually do it manually ourselves:
function doOnIncoming(req, res){ res.end('Welcome to LuckyLove!') }
function doOnError(infoOnError){ console.log(infoOnError); }
const server = http.createServer();
server.listen(80);
server.on('request', doOnIncoming);
server.on('clientError', doOnError);
Our return server object has another edit function called "on". The "on" function doesn't do anything in JavaScript, inly in Node. By the help of "on", we can match between an event name built into Node, and a javascript function we had defined early on. When that event is then triggered by Node, Node will trigger the running of the function we gave it as a match. We are now seeing here that behind the scenes, we actually have 2 built-in events called 'request' & 'clientError'. Previously, createServer was using one of them, even though we weren't aware of this, when we ran http.createServer like this:
http.createServer(doOnIncoming);
What it actually did behind the scenes is this:
server.on('request', doOnIncoming);
Basically, this was saying: "When a good message comes along, i.e. when the request event is emitted, invoke the function called doOnRequest". But what if a bad request comes along? For that we have an event name known as "clientError". So together, we have two:
server.on('request', doOnRequest);
server.on('clientError', doOnError);
And these are the explicit way to do so, to attach a function to a certain event that's emitted.