Express: Question: Why does the express router call the params keys?

Created on 1 Aug 2017  路  5Comments  路  Source: expressjs/express

So I am debugging how to test an express router object. Essentially to have 100% coverage. Anyways, what I noticed while debugging is that the router object creates a stack of routes. Cool, except when I look at the stack, params are undefined while keys is an array of my params. Pretty misleading. Then in my service, I can request these so called keys as req.params.

Just wonder why the params do not get populated here. Is this params property only for inherited params? like from router.param

question

Most helpful comment

@JemiloII In Layer.prototype.match() of router/layer.js,

Layer.prototype.match = function match(path) {
  var match

  if (path != null) {
    //...
    match = this.regexp.exec(path) // try match the path
  }

  if (!match) { // current match failed, layer.params is undefined and not populated
    this.params = undefined;
    this.path = undefined;
    return false;
  }
  // If matched
  var keys = this.keys;
  var params = this.params;

  for (var i = 1; i < match.length; i++) {
    var key = keys[i - 1];
    var prop = key.name;
    var val = decode_param(match[i]) // using the grouping of regexp

    if (val !== undefined || !(hasOwnProperty.call(params, prop))) {
      params[prop] = val; // populate layer.params HERE!
    }
  }
  return true;
}

Also after reviewing the logic of router.handle(), I believe the steps could indeed be understood as:

  1. router.handle() is invoked with a new given path that requires inspection
  2. A path match on some layer stored on the stack is found
    2.1 before this path match returned true, layer.params is populated using the param names stored in layer.keys as keys to establish key value mappings
  3. router.handle then merges current layer.params into req.params
    3.1 router.handle first try processing all param handlers added through router.param()
    3.2 router.handle then calls your provided path handlers, by internally calling layer.handle_request(req, res, next), and that all param data could be accessed through req.params
  4. if you call next() in your provided handler, the router.handle() continues tracing down the router.stack. For each extra match, goto 2. Else done.

So basically I believe you are correct.

All 5 comments

Well, the names of those properties were from before I managed Express.js so I don't know why, only that they are and changing them is not simple with a lot of code floating around using those properties.

Do you mean router.params? This contains only all callbacks added through router.param(). In lib/router/index.js

proto.param = function param(name, fn) {
    //...
    (this.params[name] = this.params[name] || []).push(fn);
    return this;
}

Or do you mean layer.params of each layer stored on router.stack? In this case, in function Layer() of lib/router/layer.js

this.regexp = pathRegexp(path, this.keys = [], opts); // regexp group fills this.keys with parameter names

layer.params is only populated as layer.params[paramName] === paramValue when layer.match() is called in router.handle() (aka router()), and that a match exist to the corresponding path. I am not quite sure what could have been the problem, but I would suggest checking the source code of Layer.prototype.match() in lib/router/layer.js and possibly proto.handle() in lib/router/index.js, to get a better understanding of them.

@kevinkassimo so the in the stack the params will get populated once the route is being navigated to? While the keys are simply there for reference? Also thanks for the direction of where to read.

@JemiloII In Layer.prototype.match() of router/layer.js,

Layer.prototype.match = function match(path) {
  var match

  if (path != null) {
    //...
    match = this.regexp.exec(path) // try match the path
  }

  if (!match) { // current match failed, layer.params is undefined and not populated
    this.params = undefined;
    this.path = undefined;
    return false;
  }
  // If matched
  var keys = this.keys;
  var params = this.params;

  for (var i = 1; i < match.length; i++) {
    var key = keys[i - 1];
    var prop = key.name;
    var val = decode_param(match[i]) // using the grouping of regexp

    if (val !== undefined || !(hasOwnProperty.call(params, prop))) {
      params[prop] = val; // populate layer.params HERE!
    }
  }
  return true;
}

Also after reviewing the logic of router.handle(), I believe the steps could indeed be understood as:

  1. router.handle() is invoked with a new given path that requires inspection
  2. A path match on some layer stored on the stack is found
    2.1 before this path match returned true, layer.params is populated using the param names stored in layer.keys as keys to establish key value mappings
  3. router.handle then merges current layer.params into req.params
    3.1 router.handle first try processing all param handlers added through router.param()
    3.2 router.handle then calls your provided path handlers, by internally calling layer.handle_request(req, res, next), and that all param data could be accessed through req.params
  4. if you call next() in your provided handler, the router.handle() continues tracing down the router.stack. For each extra match, goto 2. Else done.

So basically I believe you are correct.

So following along here, I think the original question is answered. If not, please let us know and the issue can be reopened.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

afanasy picture afanasy  路  3Comments

snowdream picture snowdream  路  3Comments

ZeddYu picture ZeddYu  路  3Comments

ER-GAIBI picture ER-GAIBI  路  3Comments

prashantLio picture prashantLio  路  3Comments