How Do Node Web Apps Work in Production?

If you’re coming to Node from a different server-side language like PHP you may find that deploying Node apps into production is weird, scary, and confusing. What’s with all the command line tools and the weird setups you need to do on the server?, you may ask. You may be used to dropping a few files into a public_html folder on your server and expecting them to “just work”. Well, not to worry. Publishing Node apps into production is different but once you understand how they actually work all those weird steps you have to take start making perfect sense. The better you understand how Node apps work the more comfortable and confident you’ll be when it comes time to deploy them.

What is a Node app?

Understanding what a Node.js app actually is, at a very low level, is crucial to understanding the rest of the process. Fortunately, it’s very simple.

A Node web application is nothing more than a long-running process. You know, like the browser you’re reading this in or the text editor I typed this in. These are nothing more than applications running as a CPU process waiting for the system to send them a command to stop running.

An example Node app

Before we talk about Node web apps, let’s talk about regular Node apps. As you already know, Node.js is just JavaScript run outside the browser (more specifically, its run on your PC or server). There’s very little difference between the JavaScript you write for the browser and the JavaScript you’d write for a Node application. The only difference is context. So where you’d normally make use of methods that primarily manipulate the DOM on the server side with Node you’ll be using the other parts of JavaScript – the parts that are the essential building blocks of just about any programming language – like loops and conditional statements. What Node brings to the table that browser JavaScript engines do not, are the APIs for interacting with the OS, file system, network, and other goodies.

Knowing this, let’s write a real quick Node application that runs on your own computer. If you want to follow along you’ll want to have Node.js installed. Our program will be a CLI application called “nodex” (short for Node Example) that spits out a greeting when called. Let’s start:

Step 1: Initialize the project

First we’ll make a new directory and set things up real nice for ourselves:

1
mkdir nodex && cd $_

Call npm init and then just answer the questions however you’d like.

1
2
npm init
# Now answer some questions - there are no wrong answers

Step 2: Create the application

Yep, we’re going to jump right into the app because that’s how easy Node is to get started with. Here we’re going to create a file, make it executable, then write the code to write our greeting to the console.

1
2
touch index.js # Creates the main file
chmod +x index.js # Makes the file executable so we can call it from the terminal

Now to write the greeting code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env node
/**
 * Node Example (nodex)
 * The coolest Node app you've ever seen. Don't be too jelly.
 */

// Slice off the 'node' and 'app-name' parameters from ARGV
// What's left is only the arguments you want to pass to your application
var args      = process.argv.slice(2),
    exitCode  = 0; // Default to a successful exit code

// Now let's determine how to greet people

// This statement lets us greet multiple people at the same time
if (args.length > 1) {
  console.log('Hello there ' args.join(' and '));
} else if (args.length === 0 && args[0].toLowerCase() === 'william') {
  // Now we create a special case for people with the name "William"
  console.warn('Hey William. I have a special greeting for you. Watch my finger...');
} else if (args.length > 0) {
  // Then we have our default greeting for everyone else
  console.log('Greetings, ' + args[0] + '.');
} else {
  // And if no name was supplied we print an error message and change the exit
  // code to reflect an error
  console.warn('Missing or invalid arguments supplied. I am not equipped to handle this situation.');
  exitCode = 1;
}

// Exit the application with the correct code
process.exit(exitCode);

Note: The first line of the script #!/usr/bin/env node tells the system that this is a Node app and it should run it with the Node executable found in the path specified

This was incredibly simple and demonstrates how Node.js applications work whether they’re command line applications or server side web applications. They’re just a running process that starts and ends like any other.

Step 3: Run the Node app

To run our example all you need to do is call ./index.js Your-first-name from the command line and you’ll get a greeting. Simple, right? So why then is it so hard to get a web app running on your server?

Running Node web apps in production

We’ve seen that a Node.js application – web app or CLI app/script – is really just nothing more than a bunch of commands you can enter in the terminal. So how do we translate this concept to a web application? How do we get a simple script like this on a server, responding to requests.

There are two main ways to run Node apps in production. Either directly or via a proxy. Note that running a Node app directly is what you do when you run your Node web application locally. It binds itself to a port on localhost and responds to requests directly from there. I prefer and recommend the proxy approach. Either way there’s setup involved so one way isn’t easier than another. Running the Node app directly would mean messing with your server’s network settings, iptables, firewalls, and all manner of stuff that’s just a big hair mess to understand. Using a proxy has the advantage of allowing you to serve static files and apps written in other languages as well as your Node app all at once. For Node apps, I run Nginx as a proxy to them. I have a website that is made of all static files with an API running a Node app in a subdirectory. So all requests to http://mywebsite.com/<a bunch of subdirectories here> will serve my static HTML but requests to http://mywebsite.com/app/ will forward the requests on to my Node application.

The image below shows a diagram of how this works. Unlike the usual virtual host configuration you may be used to where a site has one main folder where all your site files are served from, when you have a static site plus a proxied Node application in a subdirectory you don’t need to host both pieces of your site in the same place. In this example we have a directory containing a public_html/ folder and an api/ folder both on the same level. public_html/ holds static HTML files like you’re used to an the api/ directory will contain the Node.js application that your server will be running

Node.js/static website hybrid folder structure diagram

When in production, your site will be accessible from port 80 like usual while your Node app will be running on port 3000 (or any port you choose). Nginx will be configured so that any requests to yoursite.com/api/ will be forwarded to localhost:3000 which will then send the response back to Nginx and then back to the user. This is how you’re able to run multiple Node, Ruby, or really any other web applications simultaneously on different ports while still allowing them to be accessible from your domain. From a user’s perspective, they’re hitting your website just like they would any other and there’s nothing special about it.

The following snippet is part of a configuration for an Nginx virtual host that you can use to serve static files out of the usual public_html directory and a Node.js application out of a completely different directory having nothing to do with public_html. This config corresponds to the diagram shown above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# HTTPS server
#
server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server ipv6only=on;
  server_name mywebsite.com;

  root /var/www/mywebsite.com/public_html;
  index index.html index.htm;

  # Error pages
  error_page 404 /404.html;

  # BEGIN API Config
  location ~ ^/api($|/.*$) { # All requests to mywebsite.com/api/ will be caught by this block
    proxy_pass http://127.0.0.1:7000$1; # Since there is no /api folder, Nginx will pass the request to the Node app running on localhost:7000

    # These are some basic headers you'll normally want to set in order to pass header data
    # about the request to the Node app to allow you to implement some security based on their values.
    # Stuff like cross-domain requests should be handled in your Node app but it needs these headers
    # in order to work.
    proxy_set_header Host $http_host;
    proxy_buffering off;
    # Begin security headers
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;

    # This makes it so that AJAX requests can only originate from your domain
    proxy_set_header 'Access-Control-Allow-Origin' 'https://mywebsite.com';

    proxy_set_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    proxy_set_header 'Access-Control-Allow-Headers' 'X-Requested-With,Accept,Content-Type, Origin';
    # End security headers
    autoindex  off;
  }
  # END API Config

  # For all other static files, the usual location block
  location / {
    try_files $uri $uri/ =404;
  }
}

Note that you can have multiple Node (or even Ruby or other technologies) apps running at the same time, all with different “public_html” (document root) directories just by duplicating the location ~ ^/api($|/.*$) { ... } block, obviously changing the name of the subdirectory, and running the additional app on an unused port. You normally see people choosing ports 3000, 8000, and 9000 for their Node apps but it shouldn’t matter so long as the port isn’t blocked or in use by another application like a mail server SSH, or some other process.

This is how I’m able to run a Ghost blog, a Node.js API, and static portions of a website I operate from what looks like a single document root when really the Ghost blog and the API are completely separate from my public_html/ static files.

The advantage of this setup is that it allows you to work on different pieces of a project using whichever technologies are best suited for it. It also allows you to more easily manage a project because each piece is independant of the other. By the same token, if you’re not careful, this can also make things a pain as well. For me, being able to deploy new static files without worrying about the API or blog being down or being able to deploy updates to the blog without worrying about the API or static files is great.

Starting the application in production

Now that you have a general concept of what a Node.js app is and how its set up on the server, you can deploy your own. Normally, when it comes time to deploy a Node application, a lot of developers new to Node think they can plop their local copy of the application on a server and start hitting it with requests. You can’t do that. You first need to start the application so it can listen for requests. This is basically the same as how you do it locally with a small exception.

Step 0: Make sure your server has the proper tools

Just as you have to make sure you have Node installed locally to develop a Node application, you also need to have almost all of the same tools installed on your server as well. So, without going into too much detail, here’s how to prepare an Ubuntu server running 12.04 or 14.04LTS.

We’re going to use a great tool called NVM to install Node. nvm is a lot like RVM for Ruby. It allows you to install, manage, and use different versions of Node in different places. This is great for projects where one app was developed against Node 0.10.30 and another against 0.10.35 (the latest of this writing), for example. To get nvm you’ll need cURL installed (usually it is but if not, just run sudo apt-get install curl).

  1. Install nvm by running curl https://raw.githubusercontent.com/creationix/nvm/v0.22.2/install.sh | bash. It’s important to note that you should not just go around executing random scripts you find online by piping the cURL output to bash. That’s dangerous. In this case, I can vouch for the safety of this script but definitely visit the URL https://raw.githubusercontent.com/creationix/nvm/v0.22.2/install.sh in your browser to inspect the code yourself if you have a concern.
  2. Install the latest version Node by running nvm install 0.10.35. nvm will then download and install that version of Node on your system.

Great, now you have all the tools you need to run Node web apps.

Step 1: Get your project online

The first thing you’ll want to do is get your files from your local machine onto your server. You can do this with a Git post-receive hook or rsync or scp or FTP. It really doesn’t matter for our purposes though I do recommend deploying your app with a Git hook (please note that the part of that article where it tells you to change folder permissions to 777 should NOT be followed – it’s a really lazy way to solve a problem).

Step 2: Start the server

Once you have your project files on your server you’ll need to install any dependencies and start the app. It’s really as simple as that. In this next step we’re going to do just that. At this point I assume you have some sort of simple connect or Express web app that works for you locally that you want to deploy. If not, bookmark this page and come back with some code that works for you locally that you want to deploy

1
2
3
4
5
6
7
8
9
ssh you@your-server.com
cd /path/to/your/node-app/document_root/
nvm use 0.10.35 # This command sets the current Node version to use

# Now that we're in our project folder we install dependencies
npm install

# To start your app you'll need to execute the main file - this will be different for everyone. I usually use 'server/index.js'
node server/index.js

Okay, great. Now the app is running but it’s taking up the entire terminal session. You can’t log out of your server without quitting the app which will then make it inaccessible from the web. Luckily there are some tools to make your app run as a daemon and automatically restart them if and when they crash.

Keeping your app running forever

Quit your Node app by hitting Ctrl + C. There’s a tool called Forever that will keep your apps running indefinitely as a daemon even if they crash. You can install it through npm with npm install -g forever.

With Forever installed on your system you can use it to start your application with forever start server/index.js (remember server/index.js is what I used for my main file, you’ll need to replace that with the path to the file that starts your Node server). Now you can try to visit your app in a browser and… nothing happens. It’s a 404 or an error page. This is because you didn’t set up the virtual host yet.

Remember that Nginx virtual host block I showed you before? You’ll need to take the API block and add it to your server { ... } block. Once this is done, save the file and run sudo nginx -t. This will test the configuration file for errors before you put it into production. Fix any errors it reports and once it passes the tests you can restart your Nginx server with sudo nginx restart.

Now you can visit your site and you’ll see the Node app is chugging away as it did in development. Now, if you wanted to run the Node app as the only and main thing on your server then instead of putting it into a location block with a subdirectory like /api like in my example, you should take that code and just put it inside of the main location block instead. It looks like this:

1
2
3
location / {
  try_files $uri $uri/ =404; # <--- You'll put the proxy settings here instead of inside of the /api block. Replace this line with those
}

Otherwise you’ll have static files served from / and your Node app will respond to all calls to /api or whatever. Now, note that Node doesn’t know that it’s inside of the /api directory. So if you have routes like /some-route and are trying to hit them from http://mywebsite.com/some-route they’re going to fail. You’ll need to add /api to them so they become http://mywebsite.com/api/some-route.

Conclusion

What did we learn today?

  • Node web applications are just long-running CPU processes
    • They will take over your current bash session like any CLI app while it runs
    • This is why you need a tool like Forever to daemonize your Node code (purposeful rhyme)
  • Nginx can be configured to forward requests to one or more Node processes and return the response
  • Node apps get loaded into memory when started and require a restart when changes are made to the code
    • This is why it’s so weird and scary to deploy Node apps
    • This is why you can’t just plop a bunch of .js files on a server and expect them to work

There is a whole hell of a lot more to building and deploying Node web apps than this. I purposely left out a lot of the pitfalls and some of the best practices here for the sake of simplicity. As you learn more and develop more complex Node apps you should eventually start to have questions and the answers to those questions will give you a more complete understanding of Node. I would absolutely not deploy an application to production using the advice contained in this article. I mean, I would if it was just a fun side project, but I wouldn’t deploy any code that needs to be reliable, stable, and easy to deploy and maintain.

This should get you started with a basic understanding of how Node apps work and are deployed. Its up to you to fill in the gaps and move on to more advanced techniques.

Web development

« Grunt task to guard against deploying the wrong branch 16 Lines of Code for a Favicon. Seriously? »

Comments