• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

expressjs / express / 16594711778

29 Jul 2025 11:24AM UTC coverage: 99.496%. First build
16594711778

Pull #6666

github

web-flow
Merge f6b8e6906 into 9420cd3f9
Pull Request #6666: fix: Add helpful error messages for invalid wildcard routes in Express v5

21 of 22 new or added lines in 1 file covered. (95.45%)

789 of 793 relevant lines covered (99.5%)

571.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.49
/lib/application.js
1
/*!
2
 * express
3
 * Copyright(c) 2009-2013 TJ Holowaychuk
4
 * Copyright(c) 2013 Roman Shtylman
5
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
6
 * MIT Licensed
7
 */
8

9
'use strict';
10

11
/**
12
 * Module dependencies.
13
 * @private
14
 */
15

16
var finalhandler = require('finalhandler');
2✔
17
var debug = require('debug')('express:application');
2✔
18
var View = require('./view');
2✔
19
var http = require('node:http');
2✔
20
var methods = require('./utils').methods;
2✔
21
var compileETag = require('./utils').compileETag;
2✔
22
var compileQueryParser = require('./utils').compileQueryParser;
2✔
23
var compileTrust = require('./utils').compileTrust;
2✔
24
var resolve = require('node:path').resolve;
2✔
25
var once = require('once')
2✔
26
var Router = require('router');
2✔
27

28
/**
29
 * Module variables.
30
 * @private
31
 */
32

33
var slice = Array.prototype.slice;
2✔
34
var flatten = Array.prototype.flat;
2✔
35

36
/**
37
 * Application prototype.
38
 */
39

40
var app = exports = module.exports = {};
2✔
41

42
/**
43
 * Variable for trust proxy inheritance back-compat
44
 * @private
45
 */
46

47
var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';
2✔
48

49
/**
50
 * Initialize the server.
51
 *
52
 *   - setup default configuration
53
 *   - setup default middleware
54
 *   - setup route reflection methods
55
 *
56
 * @private
57
 */
58

59
app.init = function init() {
2✔
60
  var router = null;
1,900✔
61

62
  this.cache = Object.create(null);
1,900✔
63
  this.engines = Object.create(null);
1,900✔
64
  this.settings = Object.create(null);
1,900✔
65

66
  this.defaultConfiguration();
1,900✔
67

68
  // Setup getting to lazily add base router
69
  Object.defineProperty(this, 'router', {
1,900✔
70
    configurable: true,
71
    enumerable: true,
72
    get: function getrouter() {
73
      if (router === null) {
4,970✔
74
        router = new Router({
1,730✔
75
          caseSensitive: this.enabled('case sensitive routing'),
76
          strict: this.enabled('strict routing')
77
        });
78
      }
79

80
      return router;
4,970✔
81
    }
82
  });
83
};
84

85
/**
86
 * Initialize application configuration.
87
 * @private
88
 */
89

90
app.defaultConfiguration = function defaultConfiguration() {
2✔
91
  var env = process.env.NODE_ENV || 'development';
1,900✔
92

93
  // default settings
94
  this.enable('x-powered-by');
1,900✔
95
  this.set('etag', 'weak');
1,900✔
96
  this.set('env', env);
1,900✔
97
  this.set('query parser', 'simple')
1,900✔
98
  this.set('subdomain offset', 2);
1,900✔
99
  this.set('trust proxy', false);
1,900✔
100

101
  // trust proxy inherit back-compat
102
  Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
1,900✔
103
    configurable: true,
104
    value: true
105
  });
106

107
  debug('booting in %s mode', env);
1,900✔
108

109
  this.on('mount', function onmount(parent) {
1,900✔
110
    // inherit trust proxy
111
    if (this.settings[trustProxyDefaultSymbol] === true
62✔
112
      && typeof parent.settings['trust proxy fn'] === 'function') {
113
      delete this.settings['trust proxy'];
60✔
114
      delete this.settings['trust proxy fn'];
60✔
115
    }
116

117
    // inherit protos
118
    Object.setPrototypeOf(this.request, parent.request)
62✔
119
    Object.setPrototypeOf(this.response, parent.response)
62✔
120
    Object.setPrototypeOf(this.engines, parent.engines)
62✔
121
    Object.setPrototypeOf(this.settings, parent.settings)
62✔
122
  });
123

124
  // setup locals
125
  this.locals = Object.create(null);
1,900✔
126

127
  // top-most app is mounted at /
128
  this.mountpath = '/';
1,900✔
129

130
  // default locals
131
  this.locals.settings = this.settings;
1,900✔
132

133
  // default configuration
134
  this.set('view', View);
1,900✔
135
  this.set('views', resolve('views'));
1,900✔
136
  this.set('jsonp callback name', 'callback');
1,900✔
137

138
  if (env === 'production') {
1,900✔
139
    this.enable('view cache');
2✔
140
  }
141
};
142

143
/**
144
 * Dispatch a req, res pair into the application. Starts pipeline processing.
145
 *
146
 * If no callback is provided, then default error handlers will respond
147
 * in the event of an error bubbling through the stack.
148
 *
149
 * @private
150
 */
151

152
app.handle = function handle(req, res, callback) {
2✔
153
  // final handler
154
  var done = callback || finalhandler(req, res, {
2,310✔
155
    env: this.get('env'),
156
    onerror: logerror.bind(this)
157
  });
158

159
  // set powered by header
160
  if (this.enabled('x-powered-by')) {
2,310✔
161
    res.setHeader('X-Powered-By', 'Express');
2,304✔
162
  }
163

164
  // set circular references
165
  req.res = res;
2,310✔
166
  res.req = req;
2,310✔
167

168
  // alter the prototypes
169
  Object.setPrototypeOf(req, this.request)
2,310✔
170
  Object.setPrototypeOf(res, this.response)
2,310✔
171

172
  // setup locals
173
  if (!res.locals) {
2,310✔
174
    res.locals = Object.create(null);
2,188✔
175
  }
176

177
  this.router.handle(req, res, done);
2,310✔
178
};
179

180
/**
181
 * Proxy `Router#use()` to add middleware to the app router.
182
 * See Router#use() documentation for details.
183
 *
184
 * If the _fn_ parameter is an express app, then it will be
185
 * mounted at the _route_ specified.
186
 *
187
 * @public
188
 */
189

190
app.use = function use(fn) {
2✔
191
  var offset = 0;
1,734✔
192
  var path = '/';
1,734✔
193

194
  // default path to '/'
195
  // disambiguate app.use([fn])
196
  if (typeof fn !== 'function') {
1,734✔
197
    var arg = fn;
128✔
198

199
    while (Array.isArray(arg) && arg.length !== 0) {
128✔
200
      arg = arg[0];
12✔
201
    }
202

203
    // first arg is the path
204
    if (typeof arg !== 'function') {
128✔
205
      offset = 1;
122✔
206
      path = fn;
122✔
207
    }
208
  }
209

210
  var fns = flatten.call(slice.call(arguments, offset), Infinity);
1,734✔
211

212
  if (fns.length === 0) {
1,734✔
213
    throw new TypeError('app.use() requires a middleware function')
2✔
214
  }
215

216
  // get router
217
  var router = this.router;
1,732✔
218

219
  fns.forEach(function (fn) {
1,732✔
220
    // non-express app
221
    if (!fn || !fn.handle || !fn.set) {
1,774✔
222
      try {
1,712✔
223
        return router.use(path, fn);
1,712✔
224
      } catch (err) {
225
        // Check if this is a path-to-regexp error related to missing parameter names
226
        if (err.message && err.message.includes('Missing parameter name')) {
12✔
227
          // Check if the path contains a bare wildcard (more precise detection)
228
          if (typeof path === 'string') {
4✔
229
            // Detect common problematic wildcard patterns
230
            var isBareWildcard = path === '*' ||
4✔
231
                               path === '/*' ||
232
                               /\/\*(?![a-zA-Z_$])/.test(path); // matches /* not followed by identifier chars
233

234
            if (isBareWildcard) {
4✔
235
              var enhancedError = new TypeError(
4✔
236
                'Invalid middleware path: "' + path + '". ' +
237
                'In Express v5, wildcard paths must have named parameters. ' +
238
                'Use "/*splat" instead of "*" or "/*". ' +
239
                'See Express v5 migration guide for details.'
240
              );
241
              enhancedError.code = 'INVALID_WILDCARD_PATH';
4✔
242
              enhancedError.originalError = err;
4✔
243
              throw enhancedError;
4✔
244
            }
245
          }
246
        }
247
        // Re-throw the original error if it's not a wildcard issue
248
        throw err;
8✔
249
      }
250
    }
251

252
    debug('.use app under %s', path);
62✔
253
    fn.mountpath = path;
62✔
254
    fn.parent = this;
62✔
255

256
    // restore .app property on req and res
257
    router.use(path, function mounted_app(req, res, next) {
62✔
258
      var orig = req.app;
114✔
259
      fn.handle(req, res, function (err) {
114✔
260
        Object.setPrototypeOf(req, orig.request)
64✔
261
        Object.setPrototypeOf(res, orig.response)
64✔
262
        next(err);
64✔
263
      });
264
    });
265

266
    // mounted an app
267
    fn.emit('mount', this);
62✔
268
  }, this);
269

270
  return this;
1,720✔
271
};
272

273
/**
274
 * Proxy to the app `Router#route()`
275
 * Returns a new `Route` instance for the _path_.
276
 *
277
 * Routes are isolated middleware stacks for specific paths.
278
 * See the Route api docs for details.
279
 *
280
 * @public
281
 */
282

283
app.route = function route(path) {
2✔
284
  try {
890✔
285
    return this.router.route(path);
890✔
286
  } catch (err) {
287
    // Check if this is a path-to-regexp error related to missing parameter names
288
    if (err.message && err.message.includes('Missing parameter name')) {
4✔
289
      // Check if the path contains a bare wildcard (more precise detection)
290
      if (typeof path === 'string') {
4✔
291
        // Detect common problematic wildcard patterns
292
        var isBareWildcard = path === '*' ||
4✔
293
                           path === '/*' ||
294
                           /\/\*(?![a-zA-Z_$])/.test(path); // matches /* not followed by identifier chars
295

296
        if (isBareWildcard) {
4✔
297
          var enhancedError = new TypeError(
4✔
298
            'Invalid route path: "' + path + '". ' +
299
            'In Express v5, wildcard routes must have named parameters. ' +
300
            'Use "/*splat" instead of "*" or "/*". ' +
301
            'See Express v5 migration guide for details.'
302
          );
303
          enhancedError.code = 'INVALID_WILDCARD_ROUTE';
4✔
304
          enhancedError.originalError = err;
4✔
305
          throw enhancedError;
4✔
306
        }
307
      }
308
    }
309
    // Re-throw the original error if it's not a wildcard issue
NEW
310
    throw err;
×
311
  }
312
};
313

314
/**
315
 * Register the given template engine callback `fn`
316
 * as `ext`.
317
 *
318
 * By default will `require()` the engine based on the
319
 * file extension. For example if you try to render
320
 * a "foo.ejs" file Express will invoke the following internally:
321
 *
322
 *     app.engine('ejs', require('ejs').__express);
323
 *
324
 * For engines that do not provide `.__express` out of the box,
325
 * or if you wish to "map" a different extension to the template engine
326
 * you may use this method. For example mapping the EJS template engine to
327
 * ".html" files:
328
 *
329
 *     app.engine('html', require('ejs').renderFile);
330
 *
331
 * In this case EJS provides a `.renderFile()` method with
332
 * the same signature that Express expects: `(path, options, callback)`,
333
 * though note that it aliases this method as `ejs.__express` internally
334
 * so if you're using ".ejs" extensions you don't need to do anything.
335
 *
336
 * Some template engines do not follow this convention, the
337
 * [Consolidate.js](https://github.com/tj/consolidate.js)
338
 * library was created to map all of node's popular template
339
 * engines to follow this convention, thus allowing them to
340
 * work seamlessly within Express.
341
 *
342
 * @param {String} ext
343
 * @param {Function} fn
344
 * @return {app} for chaining
345
 * @public
346
 */
347

348
app.engine = function engine(ext, fn) {
2✔
349
  if (typeof fn !== 'function') {
86✔
350
    throw new Error('callback function required');
2✔
351
  }
352

353
  // get file extension
354
  var extension = ext[0] !== '.'
84✔
355
    ? '.' + ext
356
    : ext;
357

358
  // store engine
359
  this.engines[extension] = fn;
84✔
360

361
  return this;
84✔
362
};
363

364
/**
365
 * Proxy to `Router#param()` with one added api feature. The _name_ parameter
366
 * can be an array of names.
367
 *
368
 * See the Router#param() docs for more details.
369
 *
370
 * @param {String|Array} name
371
 * @param {Function} fn
372
 * @return {app} for chaining
373
 * @public
374
 */
375

376
app.param = function param(name, fn) {
2✔
377
  if (Array.isArray(name)) {
42✔
378
    for (var i = 0; i < name.length; i++) {
4✔
379
      this.param(name[i], fn);
8✔
380
    }
381

382
    return this;
4✔
383
  }
384

385
  this.router.param(name, fn);
38✔
386

387
  return this;
38✔
388
};
389

390
/**
391
 * Assign `setting` to `val`, or return `setting`'s value.
392
 *
393
 *    app.set('foo', 'bar');
394
 *    app.set('foo');
395
 *    // => "bar"
396
 *
397
 * Mounted servers inherit their parent server's settings.
398
 *
399
 * @param {String} setting
400
 * @param {*} [val]
401
 * @return {Server} for chaining
402
 * @public
403
 */
404

405
app.set = function set(setting, val) {
2✔
406
  if (arguments.length === 1) {
35,394✔
407
    // app.get(setting)
408
    return this.settings[setting];
12,162✔
409
  }
410

411
  debug('set "%s" to %o', setting, val);
23,232✔
412

413
  // set value
414
  this.settings[setting] = val;
23,232✔
415

416
  // trigger matched settings
417
  switch (setting) {
23,232✔
418
    case 'etag':
419
      this.set('etag fn', compileETag(val));
1,928✔
420
      break;
1,926✔
421
    case 'query parser':
422
      this.set('query parser fn', compileQueryParser(val));
1,914✔
423
      break;
1,912✔
424
    case 'trust proxy':
425
      this.set('trust proxy fn', compileTrust(val));
1,962✔
426

427
      // trust proxy inherit back-compat
428
      Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
1,962✔
429
        configurable: true,
430
        value: false
431
      });
432

433
      break;
1,962✔
434
  }
435

436
  return this;
23,228✔
437
};
438

439
/**
440
 * Return the app's absolute pathname
441
 * based on the parent(s) that have
442
 * mounted it.
443
 *
444
 * For example if the application was
445
 * mounted as "/admin", which itself
446
 * was mounted as "/blog" then the
447
 * return value would be "/blog/admin".
448
 *
449
 * @return {String}
450
 * @private
451
 */
452

453
app.path = function path() {
2✔
454
  return this.parent
12✔
455
    ? this.parent.path() + this.mountpath
456
    : '';
457
};
458

459
/**
460
 * Check if `setting` is enabled (truthy).
461
 *
462
 *    app.enabled('foo')
463
 *    // => false
464
 *
465
 *    app.enable('foo')
466
 *    app.enabled('foo')
467
 *    // => true
468
 *
469
 * @param {String} setting
470
 * @return {Boolean}
471
 * @public
472
 */
473

474
app.enabled = function enabled(setting) {
2✔
475
  return Boolean(this.set(setting));
6,104✔
476
};
477

478
/**
479
 * Check if `setting` is disabled.
480
 *
481
 *    app.disabled('foo')
482
 *    // => true
483
 *
484
 *    app.enable('foo')
485
 *    app.disabled('foo')
486
 *    // => false
487
 *
488
 * @param {String} setting
489
 * @return {Boolean}
490
 * @public
491
 */
492

493
app.disabled = function disabled(setting) {
2✔
494
  return !this.set(setting);
6✔
495
};
496

497
/**
498
 * Enable `setting`.
499
 *
500
 * @param {String} setting
501
 * @return {app} for chaining
502
 * @public
503
 */
504

505
app.enable = function enable(setting) {
2✔
506
  return this.set(setting, true);
1,980✔
507
};
508

509
/**
510
 * Disable `setting`.
511
 *
512
 * @param {String} setting
513
 * @return {app} for chaining
514
 * @public
515
 */
516

517
app.disable = function disable(setting) {
2✔
518
  return this.set(setting, false);
16✔
519
};
520

521
/**
522
 * Delegate `.VERB(...)` calls to `router.VERB(...)`.
523
 */
524

525
methods.forEach(function (method) {
2✔
526
  app[method] = function (path) {
68✔
527
    if (method === 'get' && arguments.length === 1) {
6,900✔
528
      // app.get(setting)
529
      return this.set(path);
6,048✔
530
    }
531

532
    var route = this.route(path);
852✔
533
    route[method].apply(route, slice.call(arguments, 1));
852✔
534
    return this;
786✔
535
  };
536
});
537

538
/**
539
 * Special-cased "all" method, applying the given route `path`,
540
 * middleware, and callback to _every_ HTTP method.
541
 *
542
 * @param {String} path
543
 * @param {Function} ...
544
 * @return {app} for chaining
545
 * @public
546
 */
547

548
app.all = function all(path) {
2✔
549
  var route = this.route(path);
18✔
550
  var args = slice.call(arguments, 1);
14✔
551

552
  for (var i = 0; i < methods.length; i++) {
14✔
553
    route[methods[i]].apply(route, args);
476✔
554
  }
555

556
  return this;
14✔
557
};
558

559
/**
560
 * Render the given view `name` name with `options`
561
 * and a callback accepting an error and the
562
 * rendered template string.
563
 *
564
 * Example:
565
 *
566
 *    app.render('email', { name: 'Tobi' }, function(err, html){
567
 *      // ...
568
 *    })
569
 *
570
 * @param {String} name
571
 * @param {Object|Function} options or fn
572
 * @param {Function} callback
573
 * @public
574
 */
575

576
app.render = function render(name, options, callback) {
2✔
577
  var cache = this.cache;
162✔
578
  var done = callback;
162✔
579
  var engines = this.engines;
162✔
580
  var opts = options;
162✔
581
  var view;
582

583
  // support callback function as second arg
584
  if (typeof options === 'function') {
162✔
585
    done = options;
44✔
586
    opts = {};
44✔
587
  }
588

589
  // merge options
590
  var renderOptions = { ...this.locals, ...opts._locals, ...opts };
162✔
591

592
  // set .cache unless explicitly provided
593
  if (renderOptions.cache == null) {
162✔
594
    renderOptions.cache = this.enabled('view cache');
158✔
595
  }
596

597
  // primed cache
598
  if (renderOptions.cache) {
162✔
599
    view = cache[name];
8✔
600
  }
601

602
  // view
603
  if (!view) {
162✔
604
    var View = this.get('view');
158✔
605

606
    view = new View(name, {
158✔
607
      defaultEngine: this.get('view engine'),
608
      root: this.get('views'),
609
      engines: engines
610
    });
611

612
    if (!view.path) {
154✔
613
      var dirs = Array.isArray(view.root) && view.root.length > 1
6✔
614
        ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
615
        : 'directory "' + view.root + '"'
616
      var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
6✔
617
      err.view = view;
6✔
618
      return done(err);
6✔
619
    }
620

621
    // prime the cache
622
    if (renderOptions.cache) {
148✔
623
      cache[name] = view;
4✔
624
    }
625
  }
626

627
  // render
628
  tryRender(view, renderOptions, done);
152✔
629
};
630

631
/**
632
 * Listen for connections.
633
 *
634
 * A node `http.Server` is returned, with this
635
 * application (which is a `Function`) as its
636
 * callback. If you wish to create both an HTTP
637
 * and HTTPS server you may do so with the "http"
638
 * and "https" modules as shown here:
639
 *
640
 *    var http = require('node:http')
641
 *      , https = require('node:https')
642
 *      , express = require('express')
643
 *      , app = express();
644
 *
645
 *    http.createServer(app).listen(80);
646
 *    https.createServer({ ... }, app).listen(443);
647
 *
648
 * @return {http.Server}
649
 * @public
650
 */
651

652
app.listen = function listen() {
2✔
653
  var server = http.createServer(this)
18✔
654
  var args = Array.prototype.slice.call(arguments)
18✔
655
  if (typeof args[args.length - 1] === 'function') {
18✔
656
    var done = args[args.length - 1] = once(args[args.length - 1])
10✔
657
    server.once('error', done)
10✔
658
  }
659
  return server.listen.apply(server, args)
18✔
660
}
661

662
/**
663
 * Log error using console.error.
664
 *
665
 * @param {Error} err
666
 * @private
667
 */
668

669
function logerror(err) {
670
  /* istanbul ignore next */
671
  if (this.get('env') !== 'test') console.error(err.stack || err.toString());
672
}
673

674
/**
675
 * Try rendering a view.
676
 * @private
677
 */
678

679
function tryRender(view, options, callback) {
680
  try {
152✔
681
    view.render(options, callback);
152✔
682
  } catch (err) {
683
    callback(err);
2✔
684
  }
685
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc