How to use exact match in ExpressJs router - javascript

I have two routes as follow in my ExpressJs application
router.get("/task/", Controller.retrieveAll);
router.get("/task/seed/", Controller.seed);
If I make a request on /task/seed/ instead of Controller.seed, Controller.retrieveAll is getting called.
So basically router matches the /task/ string before it checks the proceeding string, in my case /seed.
How can I make sure that the router does check the full string (kind of exact match)?

The example you show using router.get() or app.get() does not actually occur. router.get() does not do partial matches unless you're using wildcards or regexes.
I verified that in this simple test app:
const express = require('express');
const app = express();
app.get("/task/", (req, res) => {
res.send("got /task");
});
app.get("/task/seed", (req, res) => {
res.send("got /task/seed");
});
app.listen(80);
When you request /task/seed, you get the message got /task/seed so, it does route correctly.
On the other hand, router.use() does do partial matches so this problem could occur if your actual code was using .use(), not .get(). In that case, you just need to either switch to the verb-specific .get() instead of using the generic .use() or you need to order your routes from the most specific to the least-specific so that the most-specific declaration gets a chance to match first:
router.use("/task/seed/", Controller.seed);
router.use("/task/", Controller.retrieveAll);
In case you're curious, the two main differences between router.use() and router.get() are:
.get() only matches GET requests while .use() matches all HTTP verbs.
.get() only does full path matches while .use() will match any URL path that starts with what you specify.

Execution of express router middleware functions is sequential. There is no keyword like exact, as we have in react-router to make the router check for exact path match.
To make your code work, and always when creating express routes, have the path with the higher specificity above the path with lesser specificity.
So, this should work:
router.get("/task/seed/", Controller.seed);
router.get("/task/", Controller.retrieveAll);
These earlier StackOverflow answers will be very helpful:
https://stackoverflow.com/a/32604002/6772055
https://stackoverflow.com/a/27317835/6772055

Related

When using Express Router are these two lines equivalent?

I could not find the official docs in a google search. Where are the official API docs for express.Router() and are lines 1 and 2 the same?
If so, is it just a matter of preference on which to use?
const express = require('express');
const router = express.Router();
router.get('path', callback); // line 1
router.route('path').get(callback); // line 2
There are 3 ways with which you can declare routes in your application
These are:
app.get("path", fn)
router.get("path", fn)
router.route("path").get(fn)
All of the above functions are used to generate http request routes in your application.
The app.get is the most basic one and is not recommended for larger projects. The reason being it gives less flexibility in handling routes as compared to the express.router. In express applications you will be dealing with middlewares a lot. Middlewares are functions that are executed before the controller function of your application
For example, take a look at this line
app.get("/user", verifyUser, createUser);`
Above, verifyUser is a middleware function that is called with (Request, Response, Next) arguments by the express framework, you can write your incoming request verification logic in verifyUser and can then callNextFunctionto pass the control to the next function with is thecreateUsercontroller;
You can have as many middlewares as you want in your route declaration.
What if you need to call verifyUser each time a user is created, removed, fetched or modified. For each of the actions, you need to define your routes like this:
app.get("/user", verifyUser, fetchUser);
app.put("/user", verifyUser, updateUser);
app.post("/user", verifyUser, createUser);
app.delete("/user", verifyUser, deleteUser);
In larger applications you need to defined different logics to different entities and routes.
express.router solves the above problem by providing us a flexibility to define what to do when user lands the /user route before passing the request to the middleware.
The above code in express router can be written as follows:
// file: userRoutes.js
const router = express.Router();
router.use((req, res, next) => {
verifyUser(req, res, next);
});
function verifyUser(req, res, next) {
// verification logic here
if (/** verification is successful */)
return next(); // return to the controller
res.status(400).json({ msg: "user does not exists" });
}
router.get("/user", fetchUser);
router.put("/user", updateUser);
router.post("/user", createUser);
router.delete("/user", deleteUser);
module.exports = router;
// app.js
const userRoutes = require("./userRoutes");
app.use(userRoutes);
Things gets really simplified if we chain the http request methods in the following way:
router.route("/user")
.get(fetchUser);
.put(updateUser);
.post(createUser);
.delete(deleteUser);
Note: Above code is for explanation purpose only, there may be syntax errors, the code is not expected to run as it is. User might need to alter the code to make it work.

Node.js:Route precedence and ordering (sails.js)

In my sails.js application i have two routes like this:
'/': {controller:'HomeController',action:'home'},
'GET /:category/:subcategory/:keyword':{controller:'SearchController',action:'index'
When I run the default route (/) it will always execute this route
GET /:category/:subcategory/:keyword .
Why is this happening??
The order of routes in route file is
1) /
2) GET /:category/:subcategory/:keyword
As mentioned in the comment above, your very general route /:category/:subcategory/:keyword is being hit because it must match asset urls on your homepage. This route will match any three-part path, ex:
/images/icons/smiley.png
/scripts/thirdparty/jquery.min.js
Etc!
There would be two approaches to fix this. One would be making your SearchController urls more specific. Maybe /search/:category/:subcategory/:keyword would be a good idea? This is the simplest and should clear up any conflicts with your assets right away.
But if you really need catch-all routes that can interfere with other specific routes, then the solution is to catch the specific routes first. For example, in routes.js:
'GET /images/*': 'RouteController.showAsset',
'GET /scripts/*': 'RouteController.showAsset',
'GET /styles/*': 'RouteController.showAsset',
//...
'GET /:category/:subcategory/:keyword': 'SearchController.index',
Then create a controller RouteController with the method:
showAsset: function(req, res) {
var pathToAsset = require('path').resolve('.tmp/public', req.path);
// ex should be '.tmp/public/images/icons/smiley.png'
return res.sendfile(pathToAsset);
},
You may need to add something in to check for file existence first, but this is the idea.
I found this approach worthwhile when I wanted a /:userName route that would not conflict with all of my /contact, /about, /robots.txt, /favicon.ico, etc. However, it takes work to maintain, so if you think the first approach can work for you, I would use that.

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?
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)

Get express original url input instead of the regexp

While using Expressjs, I can define middleware for routes, like this app.get('/home', fn);, but it's also possible to do the following:
const customRouter = express.Router();
customerRouter.get('/foo', fn);
customerRouter.get('/foo/:id', fn);
app.use('/custom', customRouter);
For the /home route, I can access app._router.stack, iterate through it and I can find the routes I declared by accessing an object on the stack and access the property route.
For customRouter, it almost works the same, I can access the route property, but it only gives me /foo and /foo/:id
Does anyone know how I can get the full url for it?
So a console.log would return /custom/foo or /custom/foo/:id

NodeJS routes with or without file extensions

With the following route:
app.get('/', controller.web.Home);
How would I add within '/' something which would allow a match for /, /index and /index.html? I would also like to use this approach for all other routes so that users don't see an error page when adding .html to a path.
I have seen this mentioned on the Express website, however there are no clear explanations for matching multiples. Thanks in advance.
Express uses path-to-regex for routing strings meaning you can use regular expressions or string patterns to match routes.
How would I add within '/' something which would allow a match for /, /index and /index.html
Something like this would work:
app.get('/|index|index.html', controller.web.Home);
I would also like to use this approach for all other routes so that users don't see an error page when adding .html to a path.
You can also write a small helper function that takes care of this for any route:
function htmlExt(route) {
return route + '|' + route + '.html';
}
And the use it for any route:
app.get(htmlExt('index'), controller.web.Home);
app.get(htmlExt('blog'), controller.web.Blog);
// ...
Other approaches
You can also pass in an array of paths instead so this should also work:
function htmlExt(route) {
return [route, route + '.html'];
}
app.get(htmlExt('index'), controller.web.Home);
Another way would be to use a regex. Perhaps one that accepts a route and an optional .html extension:
app.get(/index(.html)?/, controller.web.Home);
You can find other useful examples in Express Routing docs.
You can define an array of paths as the first argument:
app.get(['/', '/index' , '/index.html'], controller.web.Home);
using express 4.x
app.get('/(index*)?', controller.web.Home);
reference
http://expressjs.com/en/guide/routing.html
If you want a global approach you can use a middleware function. Put it before all of your routes.
app.use(function(req, res, next) {
var match = req.path.match(/(.*)\.html$/)
if (match !== null) {
res.redirect(match[1]);
} else {
next();
}
});
It redirects every path ending with .html to a route without this extension.
Of course the route path '/' needs to be handled separately.

Categories

Resources