Search code examples
javascriptnode.jsexpressroutessubdomain

Subdomain (host) based routing in Express


I've been Googling for some time, but can't find any useful answers. I'm trying to get a subdomain for an api on my website api.example.com. However, all answers said that I needed to change my DNS to redirect api.example.com to example.com/api, which I don't want. Is it possible to just serve api. instead of redirect to /api? How would I go about doing that?

  1. I'm using express.
  2. I don't want to use any other packages that aren't built-in.
const path = require('path'),
      http = require('http'),
      https = require('https'),
      helmet = require('helmet'),
      express = require('express'),
      app = express();

const mainRouter = require('./routers/mainRouter.js');

// security improvements
app.use(helmet());

// main pages
app.use('/', mainRouter);

// route the public directory
app.use(express.static('public'));

app.use(/* API subdomain router... */)

// 404s
app.use((req, res) => {
    res.status(404).sendFile(path.join(__dirname, "views/404.html"));
})

Solution

  • I recommend You to use nginx and separate api service.

    But because of some reasons You cannot avoid it (or You don't want it, cause You just want show prototype to customer ASAP).

    You can write middleware that will catch host from header and forward to some custom router:

    1) /middlewares/forwardForSubdomain.js:

    module.exports = 
        (subdomainHosts, customRouter) => {
          return (req, res, next) => {
            let host = req.headers.host ? req.headers.host : ''; // requested hostname is provided in headers
            host = host.split(':')[0]; // removing port part
    
            // checks if requested host exist in array of custom hostnames
            const isSubdomain = (host && subdomainHosts.includes(host));
            if (isSubdomain) { // yes, requested host exists in provided host list
              // call router and return to avoid calling next below
              // yes, router is middleware and can be called
              return customRouter(req, res, next); 
            }
    
            // default behavior
            next();
          }
        };
    

    2) api router as an example /routers/apiRouter.js:

    const express = require('express');
    const router = express.Router();
    
    router.get('/users', (req, res) => {
      // some operations here
    });
    
    module.exports = router;
    

    3) attach middleware before / handler:

    const path = require('path'),
          http = require('http'),
          https = require('https'),
          helmet = require('helmet'),
          express = require('express'),
          app = express();
    
    const mainRouter = require('./routers/mainRouter');
    
    // security improvements
    app.use(helmet());
    
    // ATTACH BEFORE ROUTING
    const forwardForSubdomain = require('./middlewares/forwardForSubdomain');
    const apiRouter = require('./routers/apiRouter');
    app.use(
      forwardForSubdomain(
        [
          'api.example.com',
          'api.something.com'
        ],
        apiRouter
      )
    );
    
    // main pages
    app.use('/', mainRouter);
    
    // route the public directory
    app.use(express.static('public'));
    
    // 404s
    app.use((req, res) => {
        res.status(404).sendFile(path.join(__dirname, "views/404.html"));
    })
    

    P.S. It does the same that in express-vhost package, look at the code