1

Is it possible with expressjs to have multiple routes calling the same resource, something like that:

app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)

I would like to be able to call users.getOne whichever params (:user_id or :username) is used in the get request.

In the users.getOne function, how can I determine wich one was used and build my query according to it?

exports.getOne = function(req, res){

  var queryParams = ? // I need help here

  Users
    .find(queryParams)
    ...

Thanks!

Possibly related: express.js - single routing handler for multiple routes in a single line

Community
  • 1
  • 1
inwpitrust
  • 561
  • 3
  • 7
  • 20

2 Answers2

4

From express's view, both of those routes will match the same set of request URLs. You only need one of them and you can name it to make more sense:

app.get('/users/:key', users.getOne);

//...
// http://stackoverflow.com/a/20988824/266795
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
exports.getOne = function(req, res) {
  var conditions = {_id: req.params.key};
  if (!OBJECT_ID_RE.test(req.params.key)) {
    conditions = {username: req.params.key};
  }
  Users.find(conditions)...

If you end up wanting this pattern in many routes throughout your code base, you can extract it into a /users/:user param and use app.param as per @alex's answer, but encapsulate the code to locate the user and stick it on to req.user so the actual route handler can just assume the user has been properly found and loaded by the time it executes, and 404 handling can be centralized as well.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • Thanks for your solution, it's working, but I'm wondering if a completely different workaround exist. I mean, at first, am I approaching the problem the right way? Is that a think to do? What would you really do if you wanted to achieve something like that? – inwpitrust Mar 05 '14 at 22:14
  • @inwpitrust no, creating the exact same route twice is not the correct approach. Peter shows a good way. – MikeSmithDev Mar 05 '14 at 22:31
  • Yes, no I mean, forget my code, but when designing a RESTful API, is Peter's solution the way you would tackle this problem ? Retrieving user's data by :id or :username, do you think I should enable this feature ? – inwpitrust Mar 05 '14 at 22:36
  • RESTful means IDs go in the URL, not usernames. But I don't hesitate to violate REST conventions because I personally think they are meh at best. Looking stuff up by other fields is a search and that should use the query string IMHO but I'm old school in that I think the query string is actually a good idea and I don't feel compelled to reimplement it as URL path parameters to make my URLs "clean". – Peter Lyons Mar 05 '14 at 23:25
3

Those are in fact, from express's view, the same route.

No, they are not. One route has :user_id parameter, another one has :username.

This would be a proper solution:

var OBJECT_ID_RE = /^[a-f\d]{24}$/i;

app.param('user_id', function(req, res, next, value, name) {
  if (OBJECT_ID_RE.test(value)) {
    next()
  } else {
    next('route')
  }
})

app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)

app.param set the prerequisite for the route to be called. This way when user_id matches a pattern, first route gets called, otherwise second one.

alex
  • 11,935
  • 3
  • 30
  • 42
  • OK, to clarify, they both match the same URLs, thus the need for `next('route')` which IMHO is a code smell that maybe you are trying to be overly fancy with your routing. I'm not a fan of fancy routing. I like dead simple, obvious, non-magic, easy-to-debug, routing. That said, your solution is indeed nicely idiomatic for express. – Peter Lyons Mar 05 '14 at 23:28
  • `next('route')` is a standard way of ignoring the particular route in the middleware. Only disadvantage is that calls depend on ordering, but it can be fixed easily. – alex Mar 05 '14 at 23:42