Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I support multiple websites on the same server with hapi.js?

Let's say I want to host my 2 websites (cats.com and dogs.com) on the same server with a single IP address (i.e. with virtual hosts). I want to write them both with hapi.js and have them running as a single process.

The sites may have overlapping paths, for instance they might both have a /about page.

How could I implement this with hapi?

like image 402
Matt Harrison Avatar asked Feb 29 '16 11:02

Matt Harrison


2 Answers

A nice way of achieving that with hapi is by putting your different sites in to separate plugins and using the vhost modifier when loading the plugin, ideally using Glue.

Here's an example:

sites/dogs.js

exports.register = function (server, options, next) {

    // Put all your routes for the site in here

    server.route({
        method: 'GET',
        path: '/',
        handler: function (request, reply) {

            reply('Dogs homepage');
        }
    });

    next();
};

exports.register.attributes = { name: 'dogs' };

sites/cats.js

exports.register = function (server, options, next) {

    // Put all your routes for the site in here

    server.route({
        method: 'GET',
        path: '/',
        handler: function (request, reply) {

            reply('Cats homepage');
        }
    });

    next();
};

exports.register.attributes = { name: 'cats' };

index.js

const Glue = require('glue');
const Hoek = require('hoek');

const manifest = {
    connections: [{
        port: 4000,
    }],
    registrations: [
        {
            plugin: {
                register: './sites/cats'
            },
            options: {
                routes: {
                    vhost: 'cats.com'
                }
            }
        },
        {
            plugin: {
                register: './sites/dogs'
            },
            options: {
                routes: {
                    vhost: 'dogs.com'
                }
            }
        }
    ]
};

const options = {
    relativeTo: __dirname
};

Glue.compose(manifest, options, (err, server) => {

    Hoek.assert(!err, err);
    server.start((err) => {

        Hoek.assert(!err, err);
        console.log('server started');
    });
});

You can then confirm that the routing works correctly with a couple of cURL commands:

$ curl -H "Host: cats.com" localhost:4000/
Cats homepage

$ curl -H "Host: dogs.com" localhost:4000/
Dogs homepage

A browser will set that Host header for you though so when you browse to http://cats.com or http://dogs.com hapi will serve you the correct content (provided your DNS is configured correctly).

like image 181
Matt Harrison Avatar answered Oct 22 '22 00:10

Matt Harrison


If you are using a Hapi version >= 17, the details have changed slightly, though the idea is the same.

We will want to have a plugin for each site. Then, once we have each site extracted into a plugin (cats and dogs below), we can compose the separate configurations using glue and serve the site with hapi.

In the example below, the plugins will not know or care which domain they are being served on.

The Code

Cats Plugin

This is the "server" that's meant to be used with cats.com. It returns the text Hello Cats! at the root path, /. In real life it would do something more useful, and you would likely have many more routes and handlers in a real project, but the idea remains the same.

// ./sites/cats.js
exports.plugin = {
  name: 'cats',
  version: '1.0.0',
  register: async function(server, options) {
    server.route({
      method: 'GET',
      path: '/',
      handler: (request, h) => {
        return 'Hello Cats!'
      }
    })
  }
}

Dogs Plugin

This is the server for the content appearing at dogs.com. It is exactly the same as the cats plugin, except it returns the text Hello Dogs!. Again, this is not a useful plugin, it is for illustration only.

// ./sites/dogs.js
exports.plugin = {
  name: 'dogs',
  version: '1.0.0',
  register: async function(server, options) {
    server.route({
      method: 'GET',
      path: '/',
      handler: (request, h) => {
        return 'Hello Dogs!'
      }
    })
  }
}

Main Server

This is where the vhost is specified, assigning plugins to hosts cats.com and dogs.com.

// ./server.js
const Hapi = require('hapi')
const Glue = require('glue')

const manifest = {
  server: {
    host: process.env.HOST || '0.0.0.0',
    port: process.env.PORT || 8080
  },
  register: {
    plugins: [
      {
        plugin: './sites/cats',
        routes: {
          vhost: 'cats.com'
        }
      },
      {
        plugin: './sites/dogs',
        routes: {
          vhost: 'dogs.com'
        }
      }
    ]
  }
}

const options = {
  relativeTo: __dirname
}

const startServer = async function () {
  try {
    const server = await Glue.compose(manifest, options)
    await server.start()
    console.log('Hapi days for Cats and for Dogs!')
  }
  catch (err) {
    console.error(err)
    process.exit(1)
  }
}

startServer()

The Result

Accessing the different versions

Starting the Server

$ node server.js
Hapi days for Cats and for Dogs!

Sampling Server Output

$ curl -H "Host: cats.com" localhost:8080/
Hello Cats!

$ curl -H "Host: dogs.com" localhost:8080/
Hello Dogs!

$ curl localhost:8080/
{"statusCode":404,"error":"Not Found","message":"Not Found"}

$ curl -H "Host: platypus.com" localhost:8080/
{"statusCode":404,"error":"Not Found","message":"Not Found"}

Note that there are no routes for the default host, so not specifying a host will result in a 404 being returned.

like image 42
Ezra Avatar answered Oct 22 '22 00:10

Ezra