Express Router: Server multiple sites using conditional routes based on domain name

41 views Asked by At

I know many people would feel the need to answer my question with "you need to reconsider your application architecture". Please don't. This is appropriate for what I am trying to accomplish and I would appreciate practical solutions.

I have some code that can serve a different set of static files based on the domain name. This allows me to run multiple static HTML sites off the same routes:

    app.get('/', (req, res) => {
      console.log('req.hostname: ', req.hostname)
      if (req.hostname === 'localhost') {
        app.use(express.static(path.join(__dirname, 'pages/site-A/')));
        res.sendFile(path.join(__dirname, 'pages/site-A/index.html'));
      } else {
        app.use(express.static(path.join(__dirname, 'pages/default/')));
        res.sendFile(path.join(__dirname, 'pages/default/index.html'));
      }
    });

This works fine. But the main site that I am building to manage these other sites is a little more complex. This site is has it's own router. Normally I would call it like:

    app.use('/', require('pages/site/routes'));

How do I conditionally use my router file in the '/' route based on the domain name?

    app.get('/', (req, res) => {
      console.log('req.hostname: ', req.hostname)
      if (req.hostname === 'localhost') {

        app.use(express.static(path.join(__dirname, 'pages/site-A/')));
        res.sendFile(path.join(__dirname, 'pages/site-A/index.html'));

      } else if (req.hostname === 'other.local') {

        // equivalent of res.send but for app.use('/', require('pages/site/routes')); 

      } else {

        app.use(express.static(path.join(__dirname, 'pages/default/')));
        res.sendFile(path.join(__dirname, 'pages/default/index.html'));

      }
    });
1

There are 1 answers

0
John On

The answer I decided on is based off examples of subdomain routing in NodeJS since it's essentially the same principle.

I create this middleware function. Its job is to allow you to pass in a hostname and the route you'd like to establish. If it matches, you get your desired result, otherwise it skips with next();

module.exports =
  (hosts, customRouter) => {
    return (req, res, next) => {
      let host = req.headers.host ? req.headers.host : ''; // requested hostname is provided in headers

      // checks if requested host exist in array of custom hostnames
      const isHost = (host && hosts.includes(host));
      if (isHost) {
        return customRouter(req, res, next);
      }

      next();
    }
  };

Back in my main express server I import this function:

const forwardForDomain = require('./domainRouting');

And then I can use it. Specify a list of domain names I want to use/hit that Site-A/route file.

app.use(
  forwardForDomain(
    [
      'localhost',
      'site-a.local'
    ],
    require('./pages/site-A/routes')
  )
);

This allows me to run multiple sites exactly the way I want, I just copy pasta the app.use() to my liking. Since I'll be managing several similar sites online each with its own domain name, this allows me to modify my /etc/hosts file to point .local domains for each site to the server for development purposes, as well as the actual domain names for when in production.

app.use(
  forwardForDomain(
    [
      'site-a-domain.com',
      'site-a.local'
    ],
    require('./pages/site-A/routes')
  )
);

app.use(
  forwardForDomain(
    [
      'site-b-domain.com',
      'site-b.local'
    ],
    app.use(express.static(path.join(__dirname, 'pages/site-b-html/')))
  )
);

var reactInterfaceC = require('./pages/site-c/routes')
app.use(
  forwardForDomain(
    [
      'site-c-domain.com',
      'site-c.local'
    ],
    reactInterfaceC
  )
);