I am Hack Sparrow
Captain of the Internets.

vhost in Express.js

Wondering how to replicate the Apache vhost feature in Express.js? In case you are new to vhost, it allows a web server to listen on a single port and serve different web apps depending on the domain name or the subdomain. Implementing such a feature in Express.js is very easy.

Before we can see how vhost is implemented in Express.js, we would need to play a little trick on our PC:

$ sudo vi /etc/hosts

Append these entries to the file:

127.0.0.1    apple.localhost
127.0.0.1 book.localhost

In case you don't know how to use the vi editor, refer this vi tutorial, or use a GUI-based text editor.

Windows users will find the hosts file in 'C:/Windows/System32/drivers/etc/hosts'. Make sure to run the text editor as Administrator, else you won't be able to save the file.

By making those entries in the hosts file, we are telling our PC "when you see apple.localhost or book.localhost, connect to yourself (localhost / 127.0.0.1), don't bother doing any DNS lookups".

So we have two different domain names in place to witness vhost in action in Express.js.

Time to begin the exercise.

Create a directory named 'vhost_example', create a directory under it called 'websites'. Under 'websites', create two directories 'apple.localhost', and 'book.localhost':

$ mkdir vhost_example
$ mkdir vhost_example/websites
$ mkdir vhost_example/websites/apple.localhost
$ mkdir vhost_example/websites/book.localhost

Now create an express app in 'vhost_example/websites/apple.localhost':

$ cd vhost_example/websites/apple.localhost
$ express
$ npm install

Edit package.json:

{
"name": "apple.localhost",
"version": "0.0.1",
"private": true,
"main": "app",
"dependencies": {
"express": "3.0.4",
"jade": "*"
}
}

Edit app.json:

var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path');

var app = express();

app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});

app.get('/', function(req, res) {
res.send('APPE');
});

module.exports = app;

The above changes has converted the app into a module, which can be included in other Node.js apps. Also, we have edited the handler for the route '/' to just print 'APPLE'. And, note: we have removed the listening capability of this app. Why? Because vhost app are loaded by the 'main' app, which will do the listening for the vhost apps in Express.

Do the same thing for 'book.localhost', and make sure you replace 'apple' with 'book', and 'APPLE' with 'BOOK', in the right places.

With that we have two apps converted into Node modules, which can be included in other Node.js app. What now?

Create an express app in the 'vhost_example' directory. You will be told the directory is not empty, and asked you if you want to continue. Press Y, and continue.

$ cd ..
$ express
$ npm install

We are gonna modify the 'app.js' file of this Express app to invoke the vhost magic. Here is the modified app.js file.

var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path');

var app = express();

// vhost apps
var apple_localhost = require('./websites/apple.localhost');
var book_localhost = require('./websites/book.localhost');

app.configure(function(){
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());

// apply the vhost middleware, before the router middleware
app.use(express.vhost('apple.localhost', apple_localhost))
app.use(express.vhost('*.localhost', book_localhost));

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});

app.get('/', routes.index);

http.createServer(app).listen(app.get('port'), function(){
console.log("vhost enabled Express server listening on port " + app.get('port'));
});

Noticed the changes we made?

Here we include the Node modules we created out of the two Express apps, we created earlier:

var apple_localhost = require('./websites/apple.localhost');
var book_localhost = require('./websites/book.localhost');

Here we call the vhost middleware and prepare it to handle our domain names. Don't know what middlewares are? Read up here.

app.use(express.vhost('apple.localhost', apple_localhost))
app.use(express.vhost('book.localhost', book_localhost));

I have mentioned in the comments, but will repeat again here: adding the vhost middleware after app.use(app.router) will render the vhost middleware useless, so make sure you do the app.use(express.vhost ... thing before app.use(app.router).

Now start the server:

$ node app
vhost enabled Express server listening on port 3000

Now open http://apple.localhost:3000/ and http://book.localhost:3000/ in your browser and be dazzled by the vhost magic of Express.js!

Great! Now can I host hundreds of websites like this? Yes, you can. But, should you?

When you use the vhost middleware, every time a request come to the 'main' server, the request is passed to the following function, for each domain name:

function vhost(hostname, server){
if (!hostname) throw new Error('vhost hostname required');
if (!server) throw new Error('vhost server required');
var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$', 'i');
if (server.onvhost) server.onvhost(hostname);
return function vhost(req, res, next){
if (!req.headers.host) return next();
var host = req.headers.host.split(':')[0];
if (!regexp.test(host)) return next();
if ('function' == typeof server) return server(req, res, next);
server.emit('request', req, res);
};
};

The conditions checking, and especially, the regular expression operations and validations, are costly operations. Node.js is known for its speed because of its async-ness, condition checking and regex are not async. Imagine doing this for your hundred cutting-edge apps. Your Express app will probably start to crawl a little bit. Do you still wanna run hundred apps via vhost? Maybe, not anymore :)

So what is the solution?

Enter front-facing HTTP proxies (a.k.a reverse proxies), like nginx and HAProxy. Heck, you can even use Apache as a proxy.

Using a reverse proxy, you can route the requests for each domain to its respective independent Express app. These proxies are designed especially for such tasks and are much more efficient than using an Express app as a proxy for other apps.

Looks like the topic of my next blog post is gonna be: how to set up a front-facing proxy for an Express.js app?

I guess this brings us to the end of the vhost in Express.js tutorial. You might be like "Hey Cap'n, what's the point of this tutorial, if we are not supposed to use vhost?". Well, you learnt a lot more about Express, in the process, didn't ya?

2 Responses to “vhost in Express.js”

  1. Jaap says:

    For a node.js based HTTP proxy, there’s node-http-proxy used by nodejitsu – https://github.com/nodejitsu/node-http-proxy

  2. bardu says:

    Got it only to work with

    var apple_localhost = require(‘./websites/apple.localhost/app’);

    Did I miss anything?

Make a Comment