Custom middleware(routing) to discriminate URL parameters

@JonL - thanks for thinking of me! I had planned to return to look at this to see if this extensibility would actually enable me to now achieve what I would want - I bit my tongue in messaging you re this though! haha.

I haven’t looked at this enough yet - I have zero JS expertise - and there probably isn’t enough commentary on the forum yet re: this due to it being so new, but I am now optimistic I might be able to achieve it. Will put aside time tomorrow first thing to see if I can get it anwhere. Thank you again!

1 Like

Send me some ambiguous URLs and I’ll try to write an example for you in a custom route.

Here you have a short example of how you could disambiguate using the same parameters for different functionality.

exports.handler = function (app) {
  const cars = ['ford', 'seat', 'toyota']
  const toys = ['ball', 'frisbee', 'stick']
  const colours = ['blue', 'red', 'green']

  app.get('/:param1/:param2/', (req, res) => {
    if (colours.includes(req.params.param2)) {
      if (cars.includes(req.params.param1))
        res.send('Car: ' + req.params.param1 + ' is ' + req.params.param2);
      else if (toys.includes(req.params.param1))
        res.send('Toy:  ' + req.params.param1 + ' is ' + req.params.param2);
      else
        res.send('This neither a car nor a toy');
    }
    else
      res.send('This is not a valid colour');
  });
}

The only problem I see here is that custom routes are loaded before the core routing functionality and if a route matches you don’t have available to reuse the createRoute function so you would need to recreate a lot of the functionality to render.

@patrick is there a way you could use a custom route just to pass data to createRoute?

To continue with the next route just call the next method. Solution could be rewriting the url a bit like:

exports.handler = function(app) {
  const cars = ['ford', 'seat', 'toyota'];
  const toys = ['ball', 'frisbee', 'stick'];
  const colours = ['blue', 'red', 'green'];

  app.get('/:param1/:param2/', (req, res, next) => {
    if (colours.includes(req.params.param2)) {
      if (cars.includes(req.params.param1)) {
        req.url = '/car' + req.url;
      } else if (toys.includes(req.params.param1)) {
        req.url = '/toy' + req.url;
      }
    }
    next();
  });
}

The sample above changes the route from /ford/red/ to /car/ford/red/. Next function will then continue to the next route until there is a route that matches the new url.

URL rewriting was suggested a few times throughout this thread but it seems it is a non-desirable solution as they want to keep the same URLs as they were using in the old project. They are migrating code to Wappler but want to keep the URLs which are all basically parameters.

Exporting createRoute could be a neat solution if you have time at some point to refactor code that is.

With all the changes to extensibility you are planning having an internal wappler api we can access would be cool so we can write simple middleware that takes advantage of what you already coded.

With my solution above you can use the old route paths, they are then internally rewritten to new routes that work with express.

With the example above, lets say you had the routes /:car/:colour/ and /:toy/:colour. These would conflict with each other and always execute the first one it encounters. The above handler now checks the first 2 parameters and detects if it is a car or toy and prefixes the url. You now change the routes to /car/:car/:colour/ and /toy/:toy/:colour/ and /ford/red/ or /ball/blue/ will execute the correct actions.

That is the approach I would follow and the one I suggested to @mgaussie but for whatever reason it is still undesired. Maybe it is a SEO thing as I believe it is a directory of services. And with rewrites they may lose some historic authority on the URL.

Edit: it could be paired though with a 301 response. It seems that since 2016 301’s do not lose pagerank. Assuming SEO is the reason for not wanting to rewrite the URL.

Still I think refactoring to allow us to reuse some inside routing functions would be nice for custom route middleware. But I know this is something that would need time as there are other priorities.

Thanks @JonL and @patrick.

I did end up getting it working, and completed (pre full prod launch while I work on the app part) based on your suggestions, appending a single letter in the full URL path.

However, my most desirable outcome would be to replicate the exact url paths we already have.

That project is a PHP backend and they used a controller to view the URL, and calculate what should be shown on that page, even to the point where they looked at the order of the param items.

I was pretty optimistic about the middleware extension, but from the above posts from you both - I assume we are not there yet? And certainly a little above my expertise without seeing an initial working example I can play with!

I mean yes you can, but you would have to duplicate a lot of code. Basically you would end up with a fork of /lib/setup/route.js

When I first suggested middleware I didn’t have a deep look at how the routing logic works. Then I realized that we can’t reuse some functions written by patrick as they were not written originally with modularity in mind. So they would need some refactoring but this would take time and I don’t even know if it’s a thing they would want to do right now. Nonetheless I think it’s key for custom routing middleware.

So I would agree that we are not quite there yet.

1 Like

Thank you @JonL I appreciate you taking the time to explain it.

Still trying to work around this because I think it is a very interesting problem.

@patrick, do you happen to know why this won’t work? It seems to enter an infinite loop as per browser behaviour.

Being test a layout stored in /layouts and cars and toys views stored in /views

const { templateView } = require('../../../lib/core/middleware');

exports.handler = function (app) {
  const cars = ['ford', 'seat', 'toyota']
  const toys = ['ball', 'frisbee', 'stick']
  const colours = ['blue', 'red', 'green']

  app.get('/:param1/:param2/', (req, res) => {
    if (colours.includes(req.params.param2)) {
      if (cars.includes(req.params.param1))
        templateView('test', 'cars', {}, [])
      else if (toys.includes(req.params.param1))
        templateView('test', 'toys', {}, [])
      else
        res.send('This neither a car nor a toy');
    }
    else
      res.send('This is not a valid colour');
  });
}

The templateView function returns a function. You should call it like:

templateView('test', 'cars', {}, [])(req, res);
1 Like

That was dumb.

How could I miss the first line.

return async function(req, res, next)

Thanks!

@mgaussie

Here you have your middleware to do what you need. You can set your conditions to differentiate services and functions(cars and toys) and location(colours).

This only covers simple layout+content pages so if you have SCs associated to layouts or content pages you need to add more cases(thus my comment of ending with a fork of /lib/setup/routes.js.

But hopefully it sets you on the right track if you really really need this.

const { templateView } = require('../../../lib/core/middleware');

exports.handler = function (app) {
  const cars = ['ford', 'seat', 'toyota']
  const toys = ['ball', 'frisbee', 'stick']
  const colours = ['blue', 'red', 'green']

  app.get('/:param1/:param2/', (req, res) => {
    if (colours.includes(req.params.param2)) {
      if (cars.includes(req.params.param1))
        templateView('test', 'cars', {}, [])(req, res)
      else if (toys.includes(req.params.param1))
        templateView('test', 'toys', {}, [])(req, res)
      else
        res.send('This neither a car nor a toy');
    }
    else
      res.send('This is not a valid colour');
  });
}

Edit: I’ve taken the freedom to move this from the bug category to the how-to in extensions as it’s wasn’t a bug at the moment but had no solution without custom middleware.

1 Like

Wow, @JonL - thank you!!

I haven’t implemented any custom modules yet, so I’m going to try your Nanoid shortly - as I’m sure this will help me implement the above.

I am a little lost but I will try to find my way through the above. My biggest concern would be your commnt re: server connects. Currently the page renders the majority of the data on the page from the SC which gets it’s signals from the parameters on the page… this for each and every page

But do yo load SC from an AC component or is it linked to the route? AC component to load SC data is not a problem as it is done via AJAX call.

Hey Jon,

So this is a screenshot just to 100% make sure this is indeed a AC component loading SC. On load of this SC - the page then renders all of it’s data. Will I need to do anything different to the middleware you provided to ensure these SC’s work?

Screen Shot 2020-11-04 at 8.52.03 am

And the input params of the SC, that are captured from the url path (query manager).

Yeah thats the SC call AC component. There should be no problem with that.

1 Like