Complex NodeJS / Express REGEX routing - node.js

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.

Related

How do I access URL parameter after i've routed my express app to another file?

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.

Express.js unique var per request outside routing

In my express application I have a module called helpers thats is required in almost all my routes and modules. This module has a logger method that logs to fluentd (but that's unimportant). While building the data to log I'd like to add a unique identifier of the request, so that all the logs written for the same request have the same unique ID. Using a global var in the app entry point app.use doesn't work because this var would be overwritten every time a new request hits, so the global uuid will change would obviously change in case of high load or long running tasks. The res.locals is not available outside routing, so I can't use it for this matter. Is there a way to create a var that would be unique per request and available in every module or maybe a way to access the res.locals data outside routing? Thank you
EDIT
Maybe an example will help understand better the question.
Suppose I have a module called helpers.js like this:
let helpers = {};
helpers.log = (logData, logName) => {
fluentLogger.emit('', {
name: logName,
//uuid: the needed uuid,
message: logData
});
}
module.exports = helpers;
Now obviously I can do this in my app.js entry point:
app.use(function (req, res, next) {
res.locals.uuid = uuid.v4();
next();
});
and then in every loaded middleware module that requires helpers(adding a new param to the helpers.log method):
const helpers = require('helpers');
router.post('/', (req, res, next) => {
helpers.log('my log message', 'myLogName', res.locals.uuid);
next();
});
and this will normally work. But suppose a big or middle size project where there are hundreds of custom modules and models (not middlewares) and a module may require other modules that require other modules that require finally the helpers module. In this case I should pass the res.locals.uuid as a parameter to every method of every method so that I have it available in the logger method. Not a very good idea. Suppose I have a new module called dbmodel.js that is required in a middleware function:
const helpers = require('helpers');
let dbmodel = {};
dbmodel.getSomeData = (someParam) => {
//some logic
helpers.log('my log message', 'myLogName');
}
module.exports = dbmodel;
The dbmodel has no idea about the res.locals data if I don't pass it from the middleware, so the helpers.log method will also have no idea about this.
In PHP one would normally write a GLOBAL var in the application's entry point so a hypothetical logger function would have access to this global on every method request from whichever class of the application.
Hope this explanation will help :) Thank you
EDIT 2
The solution for this kind of problems is CLS. Thanks to #robertklep for the hint. A good slideshare explaining exactly the same problem (logger with unique ID) and explaining the CLS solutions can be found here: https://www.slideshare.net/isharabash/cls-and-asynclistener
I answered a very similar question here which will solve this problem.
I used to solve the problem the libraries node-uuid and continuation-local-storage. Take a look to the answer of this question and see if it helps:
NodeJS Express - Global Unique Request Id
And you want a bigger explanation, take a look here:
Express.js: Logging info with global unique request ID – Node.js
Yes you can do so by one method .
Every request comes to his routes pass that request inside the middleware.
Suppose you have
app.get('/', function(req, res) {
res.sendFile(path.join(public + "index.html"));
});
a request.
Place Middleware in it .and edit req field coming , in this way you will get the unique variable values for each request
check out this .
https://expressjs.com/en/guide/writing-middleware.html
Like this
var requestTime = function (req, res, next) {
req.requestTime = Date.now()
next()
}
app.use(requestTime)
app.get('/', function (req, res) {
var responseText = 'Hello World!<br>'
responseText += '<small>Requested at: ' + req.requestTime + '</small>'
res.send(responseText)
})
Here req.requestTime is unique for each request.

node.js: serve static web, match request url //*/web/ to file system /web/

I use node.js in a simple way to serve a static web.
...
app.use(express.static('./build'));
http.createServer(app).listen(port, ipaddress);
...
This serves the files 1:1 (with index.html as default resource), e.g.
//server/a.html -> ./build/a.html
//server/bbb/x.html -> ./build/bbb/x.html
//server/ccc/ -> ./build/index.html
But now, I need to be able to remove 'one level' of the request url, but it shall serve still the same web, e.g.
//server/aaaa/a.html -> ./build/a.html
//server/bbbb/a.html -> ./build/a.html
//server/xxxx/bbb/x.html -> ./build/bbb/x.html
//server/yyy/ccc/ -> ./build/ccc/index.html
So I need a wildcard matching in the request url. I tried this:
app.use('/\*', express.static('./build'));
http.createServer(app).listen(port, ipaddress);
But with no luck. No more page is accessible. What is wrong?
[Edited to show that the server should serve index.html as default resource]
Depending on your application, you might put express.static() on separate Router instances that are mounted on your app. For example:
var routerA = new express.Router();
// You could also reuse the same static file handler since they
// are all using the same root path
routerA.use(express.static('./build'));
// and other `routerA` route handlers ...
var routerB = new express.Router();
routerB.use(express.static('./build'));
// and other `routerB` route handlers ...
// etc.
However if you don't have your application broken up like this already, you could also specify multiple routes like:
app.use(['aaaa', 'bbbb', 'xxxx'], express.static('./build'));
Or if nothing else, you could just use a custom middleware, calling the static file handler manually (although this is kind of a hack, as it was what separate, mounted Routers were designed to help solve):
var staticHandler = express.static('./build');
app.use(function(req, res, next) {
var m = /^\/[^/]+(\/.+)$/.exec(req.url);
if (m) {
// Temporarily override the `req.url` so that the path
// concatenation will happen correctly
var oldUrl = req.url;
req.url = m[1];
staticHandler(req, res, function(err) {
// Reverting the to the original `req.url` allows
// route handlers to match the request if a file
// was not found
req.url = oldUrl;
next(err);
});
} else
next();
});
app.get('/aaa/foo', function(req, res) {
res.end('hello from /aaa/foo!');
});
My final solution is:
// serve all files from ./web directory regardless of first element in url
app.get('/:leveltoremove/*', function(req, res) {
var path = req.params[0] ? req.params[0] : 'index.html';
res.sendfile(path, {root: './web'});
});
http.createServer(app).listen(port, ipaddress);

More sophisticated static file serving under Express

Best explained by an example. Say I have a directory /images, where I have images a.png, b.png, and c.png.
Then I have a directory /foo/images, which has an image b.png, which is different than the b.png in /images.
I want it so if a request comes in for http://mydomain.com/foo/images/a.png, it will serve the image /images/a.png. But if a request comes in for http://mydomain.com/foo/images/b.png, it will get the version of b.png in /foo/images. That is, it first checks foo/images/ and if there is not file by that name, it falls back on /images.
I could do this using res.sendfile(), but I'd prefer use built-in functionality if it exists, or someone's optimized module, while not losing the benefits (caching, etc) that might be provided by the middleware.
This would intercept requests to /foo/images/ and redirect them if the file doesn't exist, still using static middleware and caching appropriately
var imageProxy = require('./imageProxy.js');
// intercept requests before static is called and change the url
app.use( imageProxy );
// this will still get cached
app.use( express.static(__dirname + '/public') );
And inside imageProxy.js:
var url = require('url');
var fs = require('fs');
var ROOT = process.execPath + '/public';
exports = function(req, res, next) {
var parts = url.parse(req.url);
// find all urls beginnig with /foo/images/
var m = parts.pathname.match(/^(\/foo(\/images\/.*))/);
if( m ) {
// see if the override file exists
fs.exists(ROOT+m[1], function (exists) {
if( !exists ) { req.url = ROOT+m[2]; }
// pass on the results to the static middleware
next();
});
}
});
If you wanted to access the original URL for some reason, it's still available at req.originalUrl

Express-js wildcard routing to cover everything under and including a path

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/

Resources