... Now, although I seem to understand what's going on in the code... I am slightly confused by the terminology.... because when I hear the term server, I think about stuff like Apache or Nginx. I am used to thinking of them being like a container that can hold my web applications. How does Node.js server differ from Nginx/Apache server? Isn't it true that a Node.js based server (i.e. code) can still be placed within something like Nginx to run? So why are both called "servers", although the Node.js code seems to be the application that can be placed and served using Nginx.

Indeed, some projects use node.js as the front-end load balancer for other servers (including Apache).

Note that node.js is not the only development stack to do this. Web development frameworks in Go, Java and Swift also do this.

Why?

In the beginning was the CGI. CGI was fine and worked OK. Apache would get a request, find that the url needs to execute a CGI app, execute that CGI app and pass data as environment variables, read the stdout and serve the data back to the browser.

The problem is that it is slow. It's OK when the CGI app was a small statically compiled C program but a group of small statically compiled C programs became hard to maintain. So people started writing in scripting languages. Then that became hard to maintain and people started developing object oriented MVC frameworks. Now we started having trouble - EVERY REQUEST must compile all those classes and create all those objects just to serve some HTML, even if there's nothing dynamic to serve (because the framework needs to figure out that there's nothing dynamic to serve).

What if we don't need to create all those objects every request?

That was what people thought. And from trying to solve that problem came several strategies. One of the earliest was to embed interpreters directly in web servers like mod_php in Apache. Compiled classes and objects can be stored in global variables and therefore cached. Another strategy was to do pre-compilation. And yet another strategy was to run the application as a regular server process and talk with the web server using a custom protocol like FastCGI.

Then some developers started simply using HTTP as their app->server protocol. In effect, the app is also an HTTP server. The advantage of this is that you don't need to implement any new, possibly buggy, possibly not tested protocol and you can debug your app directly using a web browser (or also commonly, curl). And you don't need a modified web server to support your app, just any web server that can do reverse proxying or redirects.

Why use Apache/Nginx?

When you serve a node.js app note that you are the author of your own web server. Any potential bug in your app is a directly exploitable bug on the internet. Some people are (justifiably) not comfortable with this.

Adding a layer of Apache or Nginx in front of your node.js app means you have a battle-tested, security-hardened piece of software on the live internet as an interface to your app. It adds a tiny bit of latency (the reverse proxying) but most consider it worth it.

This used to be the standard advice in the early days of node.js. But these days there are also sites and web services that exposes node.js directly to the internet. The [http.Server][1] module is now fairly well battle-tested on the internet to be trusted.