Xervo

Supercharge your Node.js Applications with Nginx

Supercharge your Node.js Applications with Nginx

Node.js and Nginx are both fast, but they're fast at different things. In order to squeeze every drop of performance out of modern applications, we need to start specializing the purpose of our tools. In this tutorial I'm going to demonstrate this specialization by separating static content from dynamic content. Node.js will handle the dynamic content and Nginx will handle the static content.

The first thing I want to demonstrate is how much gain you can realize when you use Nginx to serve up static content. Below is a comparison of running a single Modulus servo using either Node.js (Express static middleware) or Nginx.

null

The test I ran was 10,000 requests using 40 concurrent connections. Node.js averaged 862 requests/sec and Nginx averaged 1,608 requests/sec. This means Nginx can handle roughly double the throughput in my test environment.

Deploying a Basic Standalone Static Site

It's very easy to deploy a standalone static site to Modulus. The default Nginx configuration we provide handles most common cases. Here's an example of an index.html page that shows a picture of a cat.

null

Here's the contents of my html file:

<html>
  <body>
    <img src="cat.jpg" />
  </body>
</html>

I can simply deploy this folder and without any other required changes I'd have a basic static site ready to go.

$ modulus deploy -p "My Static Site"

Once deployed, I can hit the onmodulus.net url and see my cat.

null

The default Nginx configuration we provide assumes an index of index.htm or index.html, which is why it loaded my file when the I requested the root URL. I could have also requested /index.html, or if I wanted just the cat I could request /cat.jpg. Here's the server block for the default Modulus Nginx configuration:

server {
  listen 8080 default_server;

  root /mnt/app;
  index index.html index.htm;

  location / {
    try_files $uri $uri/ =404;
  }
}

Passing Through Dynamic Content to Node.js

In the next example we're going to deploy a custom Nginx configuration that passes dynamic requests to a separate Node.js application. All static requests will be handled by the Nginx process without touching Node.

null

For this example we're going to make a cat API. The pictures of cats can be found at /static/{thumbnail}, where {thumbnail} comes from JSON data returned by the route /api/cats/{name}. This means we want /static/* to be served by Nginx and /api/* to be passed through and served by Node.js. Modulus allows custom Nginx configurations to be deployed with your site by placing them in a sites-enabled folder. Here's the Nginx configuration that will provide this functionality.

server {
  listen 8080;
  server_name static-test-47242.onmodulus.net;

  root /mnt/app;
  index index.html index.htm;

  location /static/ {
	   try_files $uri $uri/ =404;
  }

  location /api/ {
	   proxy_pass http://node-test-45750.onmodulus.net;
  }
}

Since Modulus provides the default_server you must provide a unique server_name when providing custom Nginx configurations. The server_name is simply the domain that will be used to request this site. In this example I'm using the auto-generated onmodulus.net URL. If you're using custom domains, you'd want to put your custom domain in there. We've got two location blocks defined. The first, /static/, is handled directly by Nginx to serve the pictures of cats that match whatever was requested using try_files directive. If there's no match, it will return a 404 (Not Found) error message. The second, /api/, is proxy-passed to our Node.js project. Using proxy_pass simply passes the request as-is to the other endpoint, and then passes the response back to the client. I've modified my static site to work with this configuration. Here's what it looks like now:

null

As you can see I've added the above Nginx configuration file to a sites-enabled folder. I also added some cat names and thumbnail images to a static folder. Now we need a Node.js application that will act as our API. Here's a very basic example that mimics an API for our needs.

var express = require('express');
var app = express();

app.get('/api/cat/:name', function(req, res) {
  res.json({
	   thumbnail: req.params.name + '.jpg'
  });
});

app.listen(process.env.PORT || 8080);

This API accepts GETs on /api/cat/:name and then simply returns a JSON document with the passed-in name as the thumbnail. Then when you request /static/{thumbnail}, Nginx will serve the image. Let's test it out. I'm first going to request my API using GET /api/cat/chairman_meow

null

As you can see the request was properly passed through my static site to my Node.js site, which returned some JSON data. Now I have a thumbnail image that I can request using /static/chairman_meow.jpg.

null

Since our request was in the /static/ route, it was served by Nginx and returned the chairman_meow.jpg image that I deployed with my static site.

Summary

This tutorial demonstrates a common and proven architecture for serving static content vs. dynamic content. The primary goal is to let Nginx serve static content while passing through dynamic content to Node.js. Having Nginx in front of your Node.js application provides a lot more benefits beyond simply serving static content. Nginx is very powerful and can solve a lot of problems that would otherwise be solved in Node.js. For example, caching the dynamic content, displaying error pages, rate limiting, etc. Hopefully this tutorial points you in the right direction when designing your environment to get the most performance out of the technology you choose. As always, if you have comments or questions feel free to leave them below.

What is Xervo?

Xervo makes deploying applications in the public cloud or your own data center easy. Node.js, PHP, Java, Python, Nginx, and MongoDB supported. Full Docker support included in Enterprise version. It’s free to get started.

Share This Article

comments powered by Disqus