node.js & express: for loop and `app.get()` to serve articles

Problem

I'm working on a node js express application that uses app.get() to serve the different web addresses. So app.get('/',{}); serves the home page, app.get('/login'{ }); serves the login page, etc.

Is it good practice to programatically serve pages using a for loop, like in this example?

var a = ["a", "b", "c"];
a.forEach(function(leg) {
    app.get('/' + leg, function(req, res){
        res.render('index.ejs', {
            word : leg
        });
    });
});

index.ejs is just <p><%= word %> </p>

So that site.com/a, site.com/b, and site.com/c are all webpages.

I want to utilize this to run .foreach() on a list of all of the article titles (coming from a database of stored articles).

EDIT: The website allows users to submit posts, which become "articles" in a database. I want this loop to route to new submitted articles after they've been posted. If I want to do app.get('/' + newPageName); for user submitted pages AFTER I've already started the server with node server.js, how is that achieved?

Problem courtesy of: cbsm1th

Solution

Make use of middlewares to better handle the requests. I assume you will have tens/hundreds of posts, adding routes for them like what you've done, is not so elegant.

Consider the following code; I am defining routes of /posts/:legId form. We will match all requests to this route, fetch the article and render it.

If there are handful of routes to be defined you could make use of regular expression to define them.

// dummy legs
var legs = {
  a: 'iMac',
  b: 'iPhone',
  c: 'iPad'
};

app.get('/posts/:leg', fetch, render, errors);

function fetch(req, res, next) {
  var legId = req.params.leg;

  // add code to fetch articles here
  var err;
  if (!legs[legId]) {
    err = new Error('no such article: ' + legId);
  }

  req.leg = legs[legId];
  next(err);
}

function render(req, res, next) {
  res.locals.word = req.leg;
  res.render('index');
}

function errors(err, req, res, next) {
  console.log(err);

  res.locals.error = err.message;
  // render an error/404 page
  res.render('error');
}

Hope this helps, feel free to ask me any questions you may have.

Solution courtesy of: vmx

Discussion

No, you should not be generating route handlers like that. This is what route parameters are for.

When you start a path component (folder) with a : in an Express route, Express will match any URL that follows the pattern automatically, and place the actual value in req.params. For example:

app.get('/posts/:leg', function(req, res, next) {
    // This will match any URL of the form /post/something
    // -- but NOT /post/something/else
    if (/* the value of req.params.leg is valid */) {
        res.render('index.ejs', { word: req.params.leg });
    } else {
        next(); // Since the user requested a post we don't know about, don't do
                // anything -- just pass the request off to the next handler
                // function.  If no handler responds to the request, Express
                // defaults to sending a 404.
    }
});

In the real world, you'd probably determine if the leg param is valid by doing a database lookup, which entails making an async call:

app.get('/posts/:leg', function(req, res, next) {
    db.query(..., req.params.leg, function(err, result) {
        if (err) next(err); // Something went wrong with the database, so we pass
                            // the error up the chain.  By default, Express will
                            // return a 500 to the user.
        else {
            if (result) res.render('index.ejs', result);
            else next();
        }
    });
});
Discussion courtesy of: josh3736

This recipe can be found in it's original form on Stack Over Flow.