Custom middleware(routing) to discriminate URL parameters

Good luck!
Now back to my cave. I’ll be back tomorrow at beer time.

1 Like

Thank for your help Jon.

Just at a bit of a loss here, there are sites that have the exact same URL structure, e.g.

They would not have fixed parameters for every single state, industry and city i’m sure and don’t need to append with /c/ or /p/ :frowning:

Will keep trying to figure out how to do it.

Hey @JonL - sorry one final question for you, I hope, and I hope you don’t mind.

I’ve tried to create a SC only route, and using the $_GET variable, from the url parameter - but I am unable to get the url parameter unless the SC is on a page load, rather than just a route path.

Would you mind explaining what you mean when you say SC associated to that route?

Would I assign for example a SC to the layout page for example, and on the layout page have all 6 query parameters that I pass through to 6 $_GET variables on the SC?

Or would I need a separate content page (/view) for each variation of parameters, e.g. /:parameter1 /:parameter1:/parameter2 & /:parameter1/:parameter2/:parameter3 etc. and then that page is blank, but holds the SC that passes those URL parameters to the SC on each page?

Hopefully I can figure out the rest if I can figure out how first to capture the route params.

Sorry for the late reply @mgaussie I am on and off due to holidays and poor signal reception.

You can’t use $_GET.parameter in this scenario. You need to use $_PARAM.parameter

So if your url is /this/that/:param1/:param2

You access them in the SC as $_PARAM.param1 and $_PARAM.param2

1 Like

No apologies! I appreciate your help. I thought as much but couldn’t get any data with this approach either on a SC route without loading an actual page first. I’ll try again this morn now i know that’s the way to do it

@mgaussie the basics have not changed and you still need to redirect somehow but now that you can add custom middleware you could probably do all that in custom route.
Instead of having it in SC actions.

1 Like

@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