Best way to do dynamic routing with Express.js (no

2019-02-01 11:00发布

问题:

I'm trying to create a simple CMS with express.js that dynamically creates routes. It gets a JSON from a database that looks like this:

pagesfromdb = {
    home = {
        paths = ['/','/home','/koti'],
        render = 'home.ejs',
        fi_FI = {html='<h1>Hei maailma!</h1>'},
        en_US = {html='<h1>Hello World!</h1>'}
    },
    about = {
        paths = ['/about','/tietoja'],
        render = 'general.ejs',
        fi_FI = {html='Tietoja'},
        en_US = {html='About Us'}
    }
}

and iterates over the objects creating routes like so:

Object.keys(pagesfromdb).forEach(function(key) {
    var page = pagesfromdb[key];
    app.get(page.global.paths,function(req, res){
         res.render(page.render, page[language]);
    });
});

Now everything is working fine. But the problem is, every time a user modifies the content and paths, the whole node app needs to be restarted. I didin't find any API calls to remove routes.

Is there any way to safely remove the old routes set with app.get? Should I even do that?

Is there a better way to do this kind of routing? I do like this method as it allows me to use the built in function, is fast and supports regex.

I tried removing the whole app.routes with app.routes = nul but it didn't do anything, the old routes were still in place.

One thing that did indeed remove them was

delete app._router.map.get;
app._router.map.get = [];

But does this actually remove them and is it safe to use so I don't end up hijacking huge amounts of ram if the router keeps getting repopulated?

回答1:

As @supermova said in the comments, it is possible to update Express on-the-fly. Another architecture to consider is the one similar to classic CMSs (like Wordpress, for example). In other CMSs, all requests go to the same 'callback' and on every request you look up in the database what page to serve for that URL.

app.get('/*', function (req, res) {
   db.findPage({ slug: req.url}, function (err, pageData) {
       res.render('page-template', {
           pageContent: pageData.content,
           pageTitle: pageData.title
       });
   });
});

There is a significant speed decrease as a result of this method, but in the end I think it is more sane. If speed is a huge issue you can set up a caching system (like Varnish) but there will be headaches with the approach of modifying Express routes on-the-fly. For example, what if you have to scale to two web servers? How do you keep them in sync if server A gets the 'create page' request and so it knows to update its routes, but what about server B? With every request going to the database you will be able to scale horizontally better.



回答2:

I would be very careful trying to create or destroy routes at runtime. Although you can change the data structures yourself, I do not believe these are documented as API's, so you risk this breaking if/when you upgrade Express.

This could also serve as a memory constraint if you create a new route for each database object because the set of pages can grow to who knows how big.

Look at it like this... you don't want new routes; you want to add URL's to existing routes. If you are adding a route, that means you want some URL to map to some distinct function. But you are mapping each URL to the same function. In essence, you are only adding routes by side effect. What you care about is that some dynamic set of URL's map to a specific function.

Can you instead use a wildcard to map the URL pattern to a specific function, like

app.get('/pages/*', function(req, res) {
    var page = pagesfromdb[key];
    if (page != null) {
        res.render(page.render, page.language)
    }
    else {
        res.send(404);
    }
});

And manage the pagesfromdb mapping outside of the express layer. You can use direct database access, cache + database access or a timer-based refresher, whatever performs best for you.



回答3:

Adding routes can be done on the fly, as mentioned. Deleting can too, given this fuction:

function deleteRoute(url) {
  for (var i = app.routes.get.length - 1; i >= 0; i--) {
    if (app.routes.get[i].path === "/" + url) {
      app.routes.get.splice(i, 1);
    }
  }
}

(stolen from Removing a specfic mapped route in Node.js at runtime removes static mapping?)

Updating routes like this does make your app "stateful", and will probably lead to a problems if you need load balance.