87

I have a route that looks like this:

app.all('/path/:namedParam/*splat?',function(req,res,next){
  if(!req.params.length){
    // do something when there is no splat
  } else {
    // do something with splat
  }
});

however, this doesn't work - if I call path/foo/bar it hits the route, but if I call path/foo, it doesn't.

Is it possible to have an optional splat param, or do I have to use a regex to detect this?

Edit:

to be clearer, here are the requirements I'm trying to achieve:

  • the first and second params are required
  • the first param is static, the second is a named param.
  • any number of optional additional params can be appended and still hit the route.
razu
  • 1,988
  • 1
  • 15
  • 20
Jesse
  • 10,370
  • 10
  • 62
  • 81
  • 1
    what are you trying to do? if you don't need to know splat, just do `'/path/:firstParam/*'`. If you need it, do `'/path/:firstParam/:secondParam?/*'`. – Jonathan Ong Apr 05 '12 at 02:53
  • 1
    I'm looking to have the splat be optional - the first example you gave would not match `/path/foo`, (that is what my route originally looked like before I wanted an optional splat). Additionally, in your second example, adding the splat actually negates the optional second param - `/path/foo` will not match your second pattern (neither will `/path/foo/bar` for that matter...) - one of the more annoying parts of express' router. – Jesse Apr 05 '12 at 07:14
  • 1
    personally i would just go for `/path/:firstParam` and `/path/:firstParam/:secondParam/` as two separate routers with a shared controller. no need to make your urls confusing – Jonathan Ong Apr 05 '12 at 16:31
  • Having `n` endpoints is necessary for the design of the app - I'm not just routing to 1-3 params, it can be any number, so having a limit on number of params is not an option (sure, I could create 10 endpoints, but having express do that work isn't any better than doing it in a route). I can use a regex route to solve my problem (what I'm doing now), but I was hoping to have a readable option. – Jesse Apr 05 '12 at 19:18
  • its amazing that the devs didn't think to simply use parenthesis for named optional params. ie `/path/:param(/:otherOptionalParam)` – r3wt Jun 18 '17 at 01:36

8 Answers8

115

This works for /path and /path/foo on express 4, note the * before ?.

router.get('/path/:id*?', function(req, res, next) {
    res.render('page', { title: req.params.id });
});
Jochem Schulenklopper
  • 6,452
  • 4
  • 44
  • 62
Chris
  • 1,829
  • 1
  • 15
  • 22
  • 51
    Note that while this does work, if you visit `/path/foo/bar/bazzle`, `req.params.id` will equal `foo`, and `req.params[0]` will equal `/bar/bazzle`, which may trip some people up. A cleaner solution may be to define the path as `/path/*?`, which in express 4 will set `req.params[0]` to `foo/bar/bazzle`, which is probably closer to what you're looking for. – Jesse Jun 02 '15 at 06:18
  • you're better off url encoding your optional parameter - allowing anything with slashes means you will run into conflicts down the road if you want to add a path like app.get('path/:required1/:required2/:optional?*', handler) – Jordan Apr 12 '18 at 19:41
  • @Jesse Any way to make the last part required? For example I want to match `/path/foo/bar` but *not* `/path/foo`. I tried `'/path/:id/*` but `*` seems to match empty string as well. – Franklin Yu Aug 22 '18 at 21:05
77

I just had the same problem and solved it. This is what I used:

 app.get('path/:required/:optional*?', ...)

This should work for path/meow, path/meow/voof, path/meow/voof/moo/etc...

It seems by dropping the / between ? and *, the last / becomes optional too while :optional? remains optional.

Divyanshu Sah
  • 218
  • 4
  • 13
Andreas Hultgren
  • 14,763
  • 4
  • 44
  • 48
  • Cool! I just tested this (in express 3.0.0, haven't tested in 2.x) and it works. This is definitely cleaner than my RegEx hack. – Jesse Jan 23 '13 at 20:39
  • 5
    Do you know where I can read more about these predefined keywords? – János Apr 14 '14 at 15:42
  • 8
    In express 4.0x this does not appear to work anymore – regretoverflow Jan 03 '15 at 04:38
  • 2
    @chris answered this for express 4 - since that's the latest, I moved the accepted answer to his. – Jesse Apr 09 '15 at 23:57
  • Ran into a weird error using this, for numbers with zero in it, express will cut it off. For example /:id where id=904, req.params.id = 9 and req.params.0=04 (express dumps this rest here for some reason). Chris's answer fixes this – emiidee Apr 11 '15 at 23:24
  • Is this missing a closing quote or is the `, ...` part of the string? – jocull Jun 09 '16 at 20:53
  • Thanks @jocull, amazing no one noticed until now... Fixed – Andreas Hultgren Jun 10 '16 at 06:46
  • This, `?*`, worked best for my needs because `*?` does not respect `path/meow`; it only respects `path/meow/xyz` – lwdthe1 Apr 02 '19 at 18:14
18

Will this do what you're after?

app.all('/path/:namedParam/:optionalParam?',function(req,res,next){
  if(!req.params.optionalParam){
    // do something when there is no optionalParam
  } else {
    // do something with optionalParam
  }
});

More on Express' routing here, if you haven't looked: http://expressjs.com/guide/routing.html

McKay
  • 12,334
  • 7
  • 53
  • 76
Dave Ward
  • 59,815
  • 13
  • 117
  • 134
  • 2
    This solution would match `/path/foo/bar`, but not `/path/foo/bar/baz` - the `*` splat matches `.+`, which is necessary for what I'm doing - I've definitely rtfm, doesn't seem to mention it, so maybe it's not possible... – Jesse Apr 04 '12 at 22:58
7

Suppose you have this url: /api/readFile/c:/a/a.txt

If you want req.params.path to be c::

'/api/readFile/:path*

If you want req.params.path to be c:/a/a.txt:

'/api/readFile/:path([^/]*)'
yaya
  • 7,675
  • 1
  • 39
  • 38
3

Here's the current way I'm solving this problem, it doesn't appear that express supports any number of splat params with an optional named param:

app.all(/\/path\/([^\/]+)\/?(.+)?/,function(req,res,next){
  // Note: this is all hacked together because express does not appear to support optional splats.
  var params = req.params[1] ? [req.params[1]] : [],
      name = req.params[0];
  if(!params.length){
    // do something when there is no splat
  } else {
    // do something with splat
  }
});

I'd love to have this use named params for readability and consistency - if another answer surfaces that allows this I'll accept it.

Jesse
  • 10,370
  • 10
  • 62
  • 81
0

The above solutions using optional doesn't work in Express 4. And I tried couple of ways using search patterns, but not working either. And then I found this method and seems fired for unlimited nested path, http://expressjs.com/api.html#router

// this will only be invoked if the path starts with /bar from the mount point
router.use('/bar', function(req, res, next) {
  // ... maybe some additional /bar logging ...

  // to get the url after bar, you can try
  var filepath = req.originalUrl.replace(req.baseUrl, "");

  next();
});

It's match all /bar, /bar/z, /bar/a/b/c etc. And after that, you can read req.originalUrl, since params are not filled, ex. you can try compare baseUrl and originalUrl to get the remaining path.

windmaomao
  • 7,120
  • 2
  • 32
  • 36
0

I got around this problem by using a combination of a middleware that add trailing slashes to url and router.get('/bar/*?', ..., which will pick up everything after /bar/, and return undefined if it is just /bar/. If the visitor asked for /bar, the express-slash middleware will add a slash to the request and turns the request into /bar/.

williamli
  • 3,846
  • 1
  • 18
  • 30
0

To have any trailing path to end up in a named param you can add parentheses to the asterisk;

router.get('/path/:trailing(*)?', function(req, res, next) {
  console.log(req.params.trailing);
  // ...
});

This for /path/level1 would have the trailing param set to level1, and for /path/level1/level2/level3 to level1/level2/level3.

Koen.
  • 25,449
  • 7
  • 83
  • 78