67

I have a route.js which looks like this:

module.exports = function(app) {

  app.get('/tip', function(req, res) {
    res.render("tip");
  });

  app.get('/article', function(req, res) {
   res.render("article");
  });

  app.get('/article1', function(req, res) {
   res.render("article1");
  });

  app.get('/article2', function(req, res) {
   res.render("article2");
  });

  app.get('/article3', function(req, res) {
   res.render("article3");
  });

  app.get('/modules/:name', function(req, res) {
    var name = req.params.name;
    res.render('modules/' + name);
  });

  app.get('/modules/esaver/:name', function(req, res) {
    var name = req.params.name;
    res.render('modules/esaver/' + name);
  });

};

Considering i have over 200 different routes to create, i would end up with stuff like 'article1', 'article2' etc

and my app.js is like:

var express = require('express')
  ,http = require('http')
  ,fs = require('fs')
  ,path = require('path');

var app = express();

html_templates = __dirname + '/html_templates';

app.set('views', html_templates + '/views');
app.set('view engine', 'jade');

app.use('/Core', express.static(__dirname + '/Core'));


app.listen(3000, function () {
 console.log("express has started on port 3000");
});

require('./html_templates/controller/routes.js')(app);

Is there any dynamic way to create this?

Yves M.
  • 29,855
  • 23
  • 108
  • 144
wasiim_dev
  • 1,057
  • 1
  • 9
  • 22

7 Answers7

84

I would do the same thing you did for /modules/:name

app.get('/article/:id', function(req , res){
  res.render('article' + req.params.id);
});

It would be more meaningful from a rest point of view.

If you cannot do it for any particular reason you might want to do something like:

var articlesEndpoints = ['/article2', '/article3'];
articlesEndpoints.forEach(function(name) {
  app.get(name, function(req, res) {
    res.render(name);
  });
});

Is this what you meant?

Alberto Zaccagni
  • 30,779
  • 11
  • 72
  • 106
  • yes, i am looking for something similar, but i am getting an error: Error: Failed to lookup view "/article" in views directory "C:\..... ( i have used the foreach) – wasiim_dev Sep 02 '14 at 12:21
  • Are you sure that `name` is what you expect? You might want to log it and see if it is. – Alberto Zaccagni Sep 02 '14 at 12:22
  • 1
    @wasiim_dev You need to add `app.set('views', __dirname + '/views');` near the top of your code if your views are in a subdirectory `views` relative to where your Express code is located. By default Express looks for a path relative to the *current working directory*. – mscdex Sep 02 '14 at 12:26
  • yes i confirm it is name - console.log gives -> /article, /article1 etc – wasiim_dev Sep 02 '14 at 12:27
  • What is the full path that you're getting that Error you posted? – Alberto Zaccagni Sep 02 '14 at 12:39
  • Error: Failed to lookup view "/article1" in views directory "C:\inetpub\wwwroot\system\Pulse\src\branches\projectname/html_templates/views" at Function.app.render (C:\inetpub\wwwroot\system\Pulse\src\branches\projectname\node_modules\express\lib\application.js:506:17) – wasiim_dev Sep 02 '14 at 12:44
  • @wasiim_dev why do you have articles as templates, you should really have your articles inside a database, and have a single template `article` and use article_id (`req.params.id`) to pull the article from the database and do a render with that data `res.render('article', articleData)` – user3995789 Sep 02 '14 at 13:24
  • how can we remove a dynamically added route? – zegulas Feb 19 '19 at 11:56
29

Finally got it working..

In cases where I got, article1, article2 etc:

app.get('/:name(article|article2|article3)?', function(req, res) {
    var name = req.params.name;
    res.render(name);
});

In cases where I got multi level url, I created a custom function:

function geturl(url) {

  app.get('/' + url + '/' + ':name', function(req, res){
    var name = req.params.name;
    res.render(url + '/' + name);
  });

};
Yves M.
  • 29,855
  • 23
  • 108
  • 144
wasiim_dev
  • 1,057
  • 1
  • 9
  • 22
  • 1
    I got it to work without the ? at the end of the route, any significance? – Adam Fowler Nov 21 '15 at 20:26
  • @AdamF The `?` makes `req.params.name` or `:name` optional. e.g. The above path matches '/' with empty `:name` and '/article2' but not `/abc`. – Mahsa2 Dec 01 '16 at 19:39
  • You might find this on-line [Express Route Tester](http://forbeslindesay.github.io/express-route-tester/) useful. – Mahsa2 Dec 01 '16 at 19:40
9

There are many ways to implement dynamic express routes. It depends to a great extent on the structure you have implemented in your project, here I leave an example of dynamic routes and I hope it will be useful.

RouterService.js

module.exports = (function(myCustomRoutes) {
   let express = require('express');
   let router  = express.Router();
   let methods = Object.keys(myCustomRoutes); // getting methods ('get', 'post'... etc)
   let routesMethod = null;
   let url = null;

   for(i in methods) {
      routesMethod = Object.keys(myCustomRoutes[methods[i]]);
      for(j in routesMethod) {
         url = '/' + routesMethod[j];
         url += '/:' + myCustomRoutes[methods[i]][routesMethod[j]].params.join('/:');console.log(url);
         router[methods[i]](url, myCustomRoutes[methods[i]][routesMethod[j]].controller);
      }
   }

   return router;
})();

CustomRoutes.js

module.exports = (function() {
    let routes = {get: {}, post: {}};
    let routerService = require('./RouterService');

    // GET:  /dynamic1
    routes.get.dynamic1 = {
       params: [],
       controller: function(req, res, next) {
           res.send('route 1');
       }
    };

    // GET:  /dynamic2/:param1
    routes.get.dynamic2 = {
       params: [':param1'],
       controller: function(req, res, next) {
           res.send('route 2');
       }
    };
    // POST: /dynamic3/:param1/:param1
    routes.post.dynamic3 = {
       params: ['param1', 'param2'],
       controller: function(req, res, next) {
          res.send('route 3');
       }
    };

    /*
    *  Export a router with paths
    *  GET:  /dynamic1
    *  GET:  /dynamic2/:param1
    *  POST: /dynamic3/:param1/:param1
    **/
    return routerService(routes);
})();

app.js

let express = require('express');
let app = express();


/*
 *  Option 1
 *  GET:  /dynamic1
 *  GET:  /dynamic2/:param1
 *  POST: /dynamic3/:param1/:param1
 **/
 app.use(require('CustomRoutes')());


/*
 *  Option 2
 *  GET:  /api/v1/dynamic1
 *  GET:  /api/v1/dynamic2/:param1
 *  POST: /api/v1/dynamic3/:param1/:param1
 **/
 app.use('/api/v1', require('CustomRoutes')());
Alan Olivares
  • 336
  • 2
  • 13
  • the object instantiation braces () shouldn't be there on the routerservice function, that results in a NRE when performing Object.keys in the function – abhijoseph Jan 12 '20 at 16:22
7

Here is what I did to create dynamic APIs while I am in control over which API allows access to which methods. To maintain the APIs from now on, you can just edit the APIs array.

const APIs = [
    {
        route: 'order',
        methods: ['get', 'post']
    },
    {
        route: 'item',
        methods: ['get']
    },
]
APIs.forEach(api => {
    api.methods.forEach(method => {
        app[method]('/' + api.route, (req, res) => require('./routes/' + api.route)[method](req, res))
    })
})
holydragon
  • 6,158
  • 6
  • 39
  • 62
2

Here are a couple of other solutions:

app.get(^\/article(\d{1,3})?\/?$, function(req, res, next) {
  var n;
  if (req.params[0])
    n = parseInt(req.params[0], 10);

  if (!n || (n > 0 && n < 900))
    res.render('article' + (n ? n : ''));
  else
    next();
});

or use app.all for the first solution or use a generic middleware:

app.use(function(req, res, next) {
  var m = ^\/article(\d{1,3})?\/?$.exec(req.url);
  if (m) {
    var n;
    if (m[0])
      n = parseInt(m[0], 10);

    if (!n || (n > 0 && n < 900))
      return res.render('article' + (n ? n : ''));
  }
  next(); 
});
mscdex
  • 104,356
  • 15
  • 192
  • 153
  • this could partly solve the problem. How about if i have instead of article - (tip, recipe, etc, etc ) there are over 100 possibilities here. I want something that can be used as a generic for * items following a '/' – wasiim_dev Sep 02 '14 at 12:35
1

I create a new module called: jadewalker. It will create router code automatically.

We can simply add a jadewalker comment to your jade Or pug file.

//- jadewalker=/b,/b/:id
doctype html
html
 title b.jade
body
  p b.jade
  p params: #{params.id}

And add this module to our app. That's all.

var app = require('koa')()
var router = require('koa-router')();
router = require('jadewalker')(router, path.join(__dirname, 'views'));
app.use(router.routes());

We can visit our jade file by the URL http://localhost:3000/b/abc. (^∀^)

-1

It's work on my project

routesPath = path.join(__dirname, 'routes');

fs.readdirSync(routesPath).forEach(function(file) {
  require(routesPath + '/' + file)(app);
});
Le Duong
  • 3
  • 4