Kees van Lierop
Kees van Lierop

Reputation: 1013

Node Express - Route path colon parameter exception

Currently I have two routes in my app:

/invoice/:invoice returns JSON data of an Invoice document from Mongoose

/invoice/preview returns a preview of an invoice inside an HTML template (note that this doesn't always preview an existing invoice, it could also be a non-existing of which its data is supplied via url parameters, which is why the route cannot be /invoice/:invoice/preview)

Question

There should be a better way to declare these two specific routes, because the /invoice/preview route now calls both handlers, since it matches both regexes.

If we were talking in CSS selectors /invoice/:invoice:not(preview) would be the behavior I want. Unfortunately I don't find any documentation for this.

Is there any way to achieve this or any way to improve this endpoint structure?

Upvotes: 2

Views: 2131

Answers (1)

robertklep
robertklep

Reputation: 203484

Declare more specific routes first:

router.get('/invoice/preview', ...);

router.get('/invoice/:invoice', ...);

Express checks routes in order of declaration, so once it has matched a request against /invoice/preview (and provided that its handler sends back a response), the less-specific /invoice/:invoice won't be considered.

Alternatively, if :invoice should always match a specific pattern (say a MongoDB ObjectId), you can limit the route to requests matching that pattern:

router.get('/invoice/:invoice([a-fA-F0-9]{24})', ...);

That pattern doesn't match "preview", so the order wouldn't matter so much in that case.

If this isn't possible, you could create a middleware that would check if req.params.invoice matches "preview" and, if so, would pass along the request further down the handler chain:

let notIfPreview = (req, res, next) => {
  if (req.params.invoice === 'preview') return next('route');
  next();
};

router.get('/invoice/:invoice', notIfPreview, ...);
router.get('/invoice/preview', ...);

(documented here)

Upvotes: 4

Related Questions