I'm trying to have one route cover everything under /foo including /foo itself. I've tried using /foo* which work for everything except it doesn't match /foo. Observe:
var express = require("express"),
app = express.createServer();
app.get("/foo*", function(req, res, next){
res.write("Foo*\n");
next();
});
app.get("/foo", function(req, res){
res.end("Foo\n");
});
app.get("/foo/bar", function(req, res){
res.end("Foo Bar\n");
});
app.listen(3000);
Outputs:
$ curl localhost:3000/foo
Foo
$ curl localhost:3000/foo/bar
Foo*
Foo Bar
What are my options? The best I've come up with is to route /fo* which of course isn't very optimal as it would match way too much.
I think you will have to have 2 routes. If you look at line 331 of the connect router the * in a path is replaced with .+ so will match 1 or more characters.
https://github.com/senchalabs/connect/blob/master/lib/middleware/router.js
If you have 2 routes that perform the same action you can do the following to keep it DRY.
var express = require("express"),
app = express.createServer();
function fooRoute(req, res, next) {
res.end("Foo Route\n");
}
app.get("/foo*", fooRoute);
app.get("/foo", fooRoute);
app.listen(3000);
The connect router has now been removed (https://github.com/senchalabs/connect/issues/262), the author stating that you should use a framework on top of connect (like Express) for routing.
Express currently treats app.get("/foo*") as app.get(/\/foo(.*)/), removing the need for two separate routes. This is in contrast to the previous answer (referring to the now removed connect router) which stated that "* in a path is replaced with .+".
Update: Express now uses the "path-to-regexp" module (since Express 4.0.0) which maintains the same behavior in the version currently referenced. It's unclear to me whether the latest version of that module keeps the behavior, but for now this answer stands.
It is not necessary to have two routes.
Simply add (/*)? at the end of your path string.
For example, app.get('/hello/world(/*)?' /* ... */)
Here is a fully working example, feel free to copy and paste this into a .js file to run with node, and play with it in a browser (or curl):
const app = require('express')()
// will be able to match all of the following
const test1 = 'http://localhost:3000/hello/world'
const test2 = 'http://localhost:3000/hello/world/'
const test3 = 'http://localhost:3000/hello/world/with/more/stuff'
// but fail at this one
const failTest = 'http://localhost:3000/foo/world'
app.get('/hello/world(/*)?', (req, res) => res.send(`
This will match at example endpoints: <br><br>
<pre>${test1}</pre>
<pre>${test2}</pre>
<pre>${test3}</pre>
<br><br> Will NOT match at: <pre>${failTest}</pre>
`))
app.listen(3000, () => console.log('Check this out in a browser at http://localhost:3000/hello/world!'))
In array you also can use variables passing to req.params:
app.get(["/:foo", "/:foo/:bar"], /* function */);
For those who are learning node/express (just like me): do not use wildcard routing if possible!
I also wanted to implement the routing for GET /users/:id/whatever using wildcard routing. This is how I got here.
More info: https://blog.praveen.science/wildcard-routing-is-an-anti-pattern/
Related
My main express server is called app.js in Node.js.
app.use("/login", require(./routes/login));
app.use("/:id", require("./routes/users"));
When I try to access the URL parameter, it returns undefined.
I tried logging req.params:
const express = require('express');
const router = express.Router();
router.get('/dashboard', (req, res) => {
res.send(`Current Ornament Status and Data for ${req.params}`);
});
module.exports = router;
It gives me an empty array.
I suppose that it the parameter is inaccessible in another file after routing. Could you suggest a workaround?
I think you're missing some fundamental bits about express.js routing for this to make sense.
The requires line means it is loading another piece of code. So you need to show us that too.
The :id thing requires a longer explanation.
Let's say I want the server to process the URL /finduser/23
Where 23 can vary, could be just about any number. I am NOT going to write 99 different versions of router.get, right?
router.get("/finduser/1",...
router.get("/finduser/2",...
router.get("/finduser/3",...
No, what we do is turn that into a parameter
router.get("/finduser/:id",...
Then whatever number we pass turns into req.params.id, assume router passes req,res
EX: If we pass URL /finderuser/15, then req.params.id = 15
If you just pass /finduser then req.params.id gets NOTHING.
Full details are available here
http://expressjs.com/en/guide/routing.html#route-parameters
Your example:
router.get('/dashboard', (req, res)
Doesn't have ANY parameters. so req.params.id has nothing.
I'm trying to create a NodeJS Express API (route) which has the following characteristics:
It has a base path, in my case it is /web/views. This part is a static value and doesn't change for as long as the server is up.
I can do this as follows:
const BASE = '/web/views'; // defined externally/elsewhere
app.get(BASE, function handleRequest(req, res) {
// handle API request...
}
Next, I expect to be provided with a resource. Given the name of this resource, I locate a file and send it to the client.
I can do this as follows:
app.get(BASE + '/:resource', function handleRequest(req, res) {
var resource = req.params.resource;
// handle API request...
}
So on the client, I invoke it this way:
GET /web/views/header
All of this works so far... but my problem is that my 'resource' can actually be a path in itself, such as:
GET /web/views/menu/dashboard
or a longer path, such as:
GET /web/views/some/long/path/to/my/xyz
I was using the following REGEX mapping:
const DEFAULT_REGEX = '/(\*/)?:resource';
or more precisely:
app.get(BASE + DEFAULT_REGEX, function handleRequest(req, res) {
var resource = req.params.resource;
// handle API request...
}
This works with an arbitrary length path between my BASE value and the :resource identifier, but the problem is that my resource variable only has
the xyz portion of the path and not the full path (ie: /some/long/path/to/my/xyz).
I could simply cheat and strip the leading BASE from the req.url, but I though there would be a REGEX rule for it.
If anyone knows how to do such advanced REGEX routing, I'd appreciate it.
Thanks!
Sure, so I think the easiest way is to simply not worry about using Regex, but instead just use a wildcard. You lose the cool params name, but otherwise it works as you're looking for. For example:
const express = require('express');
const app = express();
const port = 3000;
const BASE = '/web/views'
app.get(`${BASE}/*`, (req, res) => {
res.send(req.url);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
If you hit http://localhost:3000/web/views/path/to/my/resource, in my example the response content will be /web/views/path/to/my/resource, so from there it's some simple string manipulation to pull the bit you want:
let resource = req.url.split('/web/views')[1];
// resource will equal /path/to/my/resource if the above URL is used
Of course you could get fancier with your string parsing to check for errors and such, but you get the idea.
You could even setup a middleware to get that resource piece for other handlers to work from:
app.use(`${BASE}/*`, (req, res, next) => {
const resource = req.url.split(BASE)[1];
req.resource = resource;
next();
});
Then all subsequent routes will have access to req.resource.
I am working on a university project and we have decided to go for MEAN technology stack. To be honest I am a beginner with NodeJS and express, more precisely this is the first time I do sth with it.
I've found that is preferable to use express.Router rather than putting all routes to express instance e.g. app.post('path', function(req, res) { ... })
So this is what I have
var express = require('express');
var app = express();
function authorizationMiddleware(req, res, next) {
...
}
// handles login, doesn't meed autorizationMiddleware
var authRouter = express.Router();
authRouter.route('/login')
.post(function (req, res) {
...
});
// handles fetching of a single, all person(s), fetching of transactions for a person
var personRouter = require('./routes/personRoutes')(Person, Transaction, autorizationMiddleware);
//handles adding of a new transaction e.g. POST /api/transactions where params such as sender, recipient and amount are passed in body
var transactionRouther = require('./routes/transactionRoutes')(Person, Transaction, autorizationMiddleware);
app.use('/api', authRouter);
app.use('/api/persons', personRouter);
app.use('/api/transactions', transactionRoutes);
app.listen(8080, function () {
console.log('Listening on port: ' + 8080);
});
As you can see I have three routers (not even sure if I have gonne too far with them), authRouter is handling login only and I have also decided to separate persons logic from transactions logic too. (maybe I could have handled creation of new transaction in a way like /api/persons/:personId/transactions but I rather liked the idea of sending all required params in body).
I would like to ask if you agree with the solution I tried. As you can see I am passing authrizationMiddleware function (handles verification of JWT token) function to router modules and using it there.
Is there maybe a better way to use the same middleware with of multiple routers or is this a legit way?
Thx in advance
Cheers!
I don't get why you use 3 Routers. The "common" way to go (or at least the way I go) is to put all the routes in the same place, except when the path is very different or the purpose is different (for example I separate the error routes from the others).
For example, let's say I need to build a rest api for an app, I would probably have paths like:
/users/:userid
/users/:userid/comments/:commentid
/locations
...
All these routes can go in the same Router and if you want, you can apply specific authentication/authorization middlewares to them:
router.get("/users/:userid",
doAuthentication, authorizeOnUserId,
userController.getUserById);
router.get("/locations",
doAuthentication, authorizeLocations,
locationController.getAllLocations);
The middlewares are called in sequence and the request is passed on to the next middleware only if there are no errors (unauthenticaed/ unhauthorized).
Then you can simply import your routes like this:
app.use('/api', router);
Using this technique allows you to have a fine grain control over your routes.
Hope this helps.
I'm trying to add a new route (/profile) to my NodeJS Express web application. I've modified my app.js file like this:
var routes = require('./routes/index');
var profile = require('./routes/profile');
app.use('/', routes);
app.use('/profile', profile);
The '/' index path works fine, my issue is with '/profile'. Whenever I try to access it, I get a 404. This is profile.js:
var express = require('express');
var router = express.Router();
router.get('/profile', function(req, res) {
var username = req.session.username;
if(username) {
res.render('profile');
} else {
res.redirect('/login');
}
});
module.exports = router;
I don't understand what I'm doing wrong because in the example express application that is generated, '/users' works fine. I basically copied that format, but it's throwing a 404. Any ideas?
In my profile.js, I had to change my GET request path to this:
router.get('/', function(req, res) {
//code
});
Otherwise, the router would be looking for /profile/profile. When I change it to /, it's just looking for the root of `/profile', or at least that's how I understand it.
To understand what you are doing wrong you should know that Node.js uses middleware functions to route your requests. To simplify you can think about it as a chain of functions.
Middleware is like a plumbing pipe, requests start at the first middleware you define and work their way “down” the middleware stack processing for each path they match.
So with the following statement you added a middleware function to handle any request starting with the root path /profile, and it is a common pattern in Node to use the use method to define the root paths.
app.use('/profile', profile);
The use method is doing part of the routing in your scenario and the statement above will match any route starting with that path, including /profile/all or /profile/12 or even /profile/go/deeper/inside.
However, you want to narrow down that routing to something more specific, so that is why you pass a router middleware function (profile in your case) to match more specific routes instead of all routes starting with /profile.
The profile middleware function is actually the next step in the chain of functions to execute, and it will start from the root path specified in the use statement, which is the reason why you need to start again with / and not with /profile. If you wanted to match a profile by ID you would do:
router.get('/:id', ...)
Which would be concatenated with the base URL (from the /use statement) and would match a request like /profile/2 or /profile/abc.
With an express app running on a node server, how would I go about recursively searching for a render file from the full path right back to the beginning of the supplied URL.
For example, if someone was to hit my server with www.somewebsite.com/shop/products/product, the render engine would first check that there is an index.jade file in shop/products/product/. If none is found it would then check shop/products/, and subsequently shop/.
var express = require('express');
var app = express();
app.get('/*', function(req, res){
res.render(req.path + '/index.jade', function(err, html){
// some loopback code which alters the path and recalls the render method
})
});
The problem is that the response object is not passed to the render callback, so I'm unable to recall render on the response. I'm looking to create a loop because the URL paths may be any number of directories deep, so I can't just assume I only need to cascade for a definitive number of times.
Anyone see a way round this?
You should be able to use the response object from the closure. I think (assuming express allows you to call res.render a second time) you could use code like this answer to achieve what you want:
var express = require('express');
var app = express();
app.get('/*', tryRender);
function tryRender(req, res){
res.render(req.path + '/index.jade', function(err, html){
if (err) {
req.path = 'mynewpath';
tryRender(req, res);
}
})
}
Note: You will need to add a base case or this function will recurse infinitely if it doesn't find a view that works :D
In the event that express doesn't allow a subsequent call to res.render, you'll probably need to find out if the file exists on the file system yourself.