Search code examples
meteoriron-router

Iron Router - Conditional routes depending on tenant


I'm working on a multi-tenant application and I'm unsure how to load routes conditionally.

I had:

   var tenant = resolveTenant();
   if (tenant === null) {

        Router.configure({
            layoutTemplate: 'main',
            notFoundTemplate: 'not-found'
        })
        Router.route('home', {
            path: '/'
        })

        Router.route('newClient', {
            path: 'signup'
        })
    } else {
        Router.configure({
            layoutTemplate: 'storeMain',
            notFoundTemplate: 'not-found'
        })
        Router.route('storeHome', {
            path: '/'
        })
    }

So if a tenant couldn't be resolved, load the main site.

This worked just fine, not pretty, but fine. However the issue is when a tenant doesn't exist.

Case:

In order to do this I need to do a call to the database. So I wrapped the conditions inside a method call with a callback:

    Router.configure({
        layoutTemplate: "loading"
    });
    var tenant = resolveTenant();
    Meteor.call("tenant.exists", tenant, function(err, exists) {
        if (tenant !== null && !exists) {
            Router.configure({
                layoutTemplate: 'noTenant'
            })
            Router.route('noTenant', {
                path: '/'
            })
        } else if (tenant === null) {

            Router.configure({
                layoutTemplate: 'main',
                notFoundTemplate: 'not-found'
            })
            Router.route('home', {
                path: '/'
            })

            Router.route('newClient', {
                path: 'signup'
            })
        } else {
            Router.configure({
                layoutTemplate: 'storeMain',
                notFoundTemplate: 'not-found'
            })
            Router.route('storeHome', {
                path: '/'
            })
        }
    })

The problem is, is it won't move from the loading template configuration after the call has completed. And if I remove the loading template route, I get the iron router landing page.

For completion, here is the "tenant.exists" method:

  "tenant.exists": function(url){
    if(url === null){
      return false
    }
    return Tenants.find({"url": url}).count() > 0;
  },

Any ideas on how to achieve this?

Edit

I did try to remove the conditions from the method call and running the query on the client-side. However the count always returned 0 when it shouldn't.

 if (tenant !== null && Tenants.find({"url": tenant}).count() === 0) {
      Router.configure({
          layoutTemplate: 'noTenant'
      })
      Router.route('noTenant', {
          path: '/'
      })
  } else if (tenant === null) {

      Router.configure({
          layoutTemplate: 'main',
          notFoundTemplate: 'not-found'
      })
      Router.route('home', {
          path: '/'
      })

      Router.route('newClient', {
          path: 'signup'
      })
  } else {
      Router.configure({
          layoutTemplate: 'storeMain',
          notFoundTemplate: 'not-found'
      })
      Router.route('storeHome', {
          path: '/'
      })
  }

Solution

  • Please allow me to suggest a much simpler approach that also lets you avoid publishing the names of all you tenants to every client while also avoiding the asynchronous side-effects of Meteor.call():

    1. Subscribe to a tenant subscription passing the tenant name as a parameter
    2. In the corresponding publication, just return the one matching tenant
    3. In an onBeforeAction handler, see if there's a tenant record at all and proceed if so, otherwise render the appropriate page.

    Router:

    var tenant = resolveTenant();
    
    Router.route('/',{
      name: 'storeHome',
      onBeforeAction: function(){
        if ( Tenants.findOne() ) this.next();
        else if ( tenant ) this.render('noTenant');
        else this.render('signup');
      },
      waitOn: function(){
        return Meteor.subscribe('tenant', tenant);
      }
    });
    

    Server:

    Meteor.publish('tenant',name,function(){
      check(name,String);
      return Tenants.find({ url: tenant });
    });
    

    Taken from patterns here.