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)
Related
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
If you have placeholders and predefined routes at the same place, the predefined ones never get called, if the placeholder was declared before the predefined ones.
Example:
router.get("/:id", fetchEntry)
router.get("/fancy-action/", doSomethingFancy)
<-- if ordered in that way, fancy-action would call fetchEntry instead of doSomethingFanc
Put the most specific routes fancy-action before the general one :id:
router.get("/fancy-action/", doSomethingFancy)
router.get("/:id", fetchEntry)
Define /fancy-action route before the /:id
router.get("/fancy-action/", doSomethingFancy)
router.get("/:id", fetchEntry)
I have an API in ExpressJS and a middleware that gets executed before each endpoint controller:
app.use(segregationMiddleware);
app.get('/some-endpoint', controller1);
app.get('/some-endpoint-2', controller2);
The segregationMiddleware is used to look for some parameters in the request and then it calculates a value that then is stored in the request object as req.locals.domain so the controllers can access it.
In each Mongoose model, I define a field named domain using a Mongoose plugin (so I don't have to do it every time). That field is used to segregate my assets. This means that when the segregationMiddleware populates req.locals.domain = 'foo' for example, if I make a model.find({}) I want to get only assets that have { domain: 'foo' }. Same thing if I try to update, save, delete, and so.
Of course, I can just simply modify the query on each controller since I have accesso to req, but I need to do it every time and I need to remember it for finds, findAndUpdate, save, and soo... sooner or later I'm gonna forget it.
I can define some hooks in Mongoose that will modify the query using a plugin so it adds the domain constraint to the query so I don't have to do it in the controller BUT I don't have the current req object in the Mongoose plugin unless I pass it, and the only way that come to my mind is to abstract the DB methods in the plugin, so in the controller, I do something like this:
model.safeFind(req, query);
And in the plugin I define safeFind like:
safeFind = () => {
const theRealQuery = Object.assign({}, query, { domain: req.locals.domain });
return this.find(query);
}
BUT, in this way, I need to redefine each DB query function (find, findOne, update, save...) and I need to remember to use the safe methods. Then again, I'm going to forget it sooner or later.
Is there a way I can still use the regular Mongoose methods from the controller and have the plugin somehow modify the query for every method using the current req object?
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
It seems that if I want to move to a "next" function in Nodejs (and possibly Javascript in general?) I cannot pass parameters to the next function.
Here is what I mean:
app.get('/webpage', SomeFunction, NextFunction);
function SomeFunction (req, res, next) {
// Do things
next();
}
function NextFunction (req, res) {
// Do other things
}
Now, if in SomeFunction I were to say next(req, res); it does not seem to work. It never gets to the method. Obviously I cannot directly pass parameters...but my question is why? How does the next function know which parameters to use? Is it because they are named the same or does it automatically pass the 1st and 2nd parameters? If NextFunction used blah, bleet instead of req, res would it still work?
This is an intentional aspect of the design of Connect (the node.js middleware that's responsible for this behaviour). The next function your middleware receives is not the next middleware in the stack; it's a function that Connect generates which asks the next middleware to handle it (as well as doing some extra stuff to handle special cases, like when there isn't a "next middleware").
If your middleware should return a response, just do so. If it shouldn't, it's implied that some later middleware should return a response. If you need to pass along data to that later part of the process, you should attach it to an appropriate part of the request object req.
For example, the bundled bodyParser middleware is responsible for populating req.rawBody and req.body based on the contents of the request body. The bundled basicAuth middleware populates req.remoteUser based on the HTTP authentication.
This is the pattern you should try to emulate: a stack of middleware, each of which does a basic incremental thing to process the request. If what you're trying to model doesn't fit into this paradigm, then you should probably just have a single function to handle the request, from which you can call all of your own application logic however you like.