Getting filetype in an Express route - node.js

I have an Express route /doc/:id which serves up the HTML representation of a document, and I want it to serve the EPUB representation when appended with ".epub". Express doesn't separate on a period, however, so if I use /doc/:id.epub sets req.params.id to "id.epub". Is there a way to have the file extension recognised as a separate parameter or do I simply need to use a regex to extract it?
I have looked at res.format, but it seems this is only effective when the Accepted header is set, which it would not be if the URL is simply typed into a browser, as far as I can see.

This works:
app.get('/doc/:filename.:ext', function(req, res) {
...
});
This requires that the part following /doc/ contains at least one period, which may or may not be an issue.

Based on the behavior you are describing, I think you might have your route matching rules out of order.
This works:
app.get('/doc/:id.epub', function(req, res, next){
res.send('id: ' + req.params.id); //match /doc/x.epub (id=x)
});
app.get('/doc/:id', function(req, res, next){
res.send('id: ' + req.params.id); //match doc/x (id=x) | doc/y.html (id=y.html)
});
If /doc:/:id is first you'd get an id of x.epub for /doc/x.epub.

Related

Support for a URL where the question mark to denote the start of queryparams is url encoded

I've got a Node/express application where our users can be directed to the application from Google Ads. The problem here is that they encode all "special characters" of the URL (Google have said this themselves).
I have an endpoint that looks like this:
app.get("/", (req, res) => {
res.send("Hello World!");
});
And I make a request to GET myurl.com/?foo=bar, my response will be what you'd expect ("Hello World!"). I can also easily pull the queryparams out of the request object.
However, if I have a campaign set up in Google Ads with some query parameters being tacked onto the end of the URL (i.e. UTM params), the request generated might look like GET myurl.com/%3Ffoo%3Dbar. Express out of the box doesn't automatically decode those characters, so when that URL gets hit, the express application will just return a 404 because there isn't an endpoint handler for /%3Ffoo%3Dbar.
What are my options here? Do I really have to write middleware to deal with this? I could use something like:
app.use(function(req, res, next) {
req.url = req.url.replace(<insert some regex here>, function() {
<insert some logic here to do the url decoding>
});
next();
});
But it feels weird to have to do this.

NodeJS - Wrong Express route being triggered

I have the following routes available for a backend...
admin.js
router.get('/contents', ...); // GET /admin/contents
router.get('/:adminID', ...); // GET /admin/[adminID]
router.put('/:adminID', ...); // PUT /admin/[adminID]
router.get('/', ...); // GET /admin
router.post('/', ...); // POST /admin
.. but in testing, the following:
PUT /admin/contents
triggers the PUT /admin/[adminID] route. But "contents" is not an ID. I understand why this is happening (i.e. it fits into the pattern), but I'm not sure what the best/common solution is to this? Ideally, I'd like it to recognize that "contents" is not an ID, and is in fact just attempting to use an unavailable endpoint.
I could use something like...
router.use('/contents', require('./admin-contents'));
but I'd prefer to limit each top-level endpoint to a single file, opposed to spreading it across so many.
Worst-case scenario, it will look for an admin with ID: "contents", and return "admin not found", but I'd prefer it to return 404, because that is not an available endpoint for /admin.
Edit #1
To clarify, adminID is a mix of letters and numbers, with either occurring in any position in the string. A regex will not work.
Also, the only route for /admin/contents is GET. Having to implement blank routes for all the other methods (PUT, PATCH, DELETE, etc) is not ideal either.
You can provide a regex after a paramenter name in the route, to avoid that scenario.
router.put('/:adminID(\\d+)', (req, res) => {
console.log(req.params.adminID); // I'm a number
});
Now adminID must be a number, otherwise it won't enter the route.
While that's not directly documented on express routing, since express uses path-to-regexp we can see their documentation for this:
And it's documented in Custom Matching Parameters
const regexpNumbers = pathToRegexp('/icon-:foo(\\d+).png')
// keys = [{ name: 'foo', ... }]
regexpNumbers.exec('/icon-123.png')
//=> ['/icon-123.png', '123']
regexpNumbers.exec('/icon-abc.png')
//=> null
UPDATE
Your suggestion of checking for even just one number in a known-length
string should work,
app.put('/:adminID((?:\\w+(?<=\\d+)(?:\\w+)?))', (req, res) => {
// I have at least 1 number
// I can have or not alpha-numeric characters
res.send(req.params.adminID);
});
The regex uses Postive lookbehind assertions which are supported without any flag since Node.js 9.11.2. So if you're using an older version, either upgrade or use the --harmony flag to run it.
You can take advantage of the fact that node interprets handlers in order:
app.put('/admin/contents', (req, res) => res.send('contents'))
app.put('/admin/:adminId', (req, res) => res.send('id'))
When you enter admin/contents, contents is returned, for any other url admin/whatever id is returned.

Handle POST request from multiple routes all at once

I am submitting a simple contact form in my website's footer (in footer.pug):
form(method="POST" action="contact_form")
input(type='email' name='ct_email' data-name='ct_email' required)
textarea(type='text' name='ct_message' data-name='ct_message' required)
button(type='submit') Send
Since the form is in a template, and the footer template is used throughout the site, the form can be submitted from various routes:
/contact_form
/route1/contact_form
/route1/de/contact_form
and so on...
So now it seems I have to create a handler for all the possible routes:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
How can I easily handle POST requests from all the routes they may be coming from without writing a handler for each?
You can use absolute path reference in your form and it will always submit to the same route even though the form is in different pages.
Try this
form(method="POST" action="/contact_form")
Notice the action changed from contact_form to /contact_form. When you add /, you start referencing the path as an absolute path to the domain. So now, from all pages, the form will be submitted to http://your-domain/contact-form.
Not entirely sure if this is what you mean, but the first argument to ExpressJS's router (I assume that's what router is doing here) can be an array. So instead of:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
You can just do:
router.post(['/contact_form','route1/contact_form'],function(req,res,next){
//some fancy logic to handle both routes.
})
Of course, this requires that you keep a list of these possible routes. On the other hand, you can follow Dinesh Pandiyan's advice, and just use an absolute path. So instead of page1.html, page2.html, page3.html, etc. all having their own own router (or own entry in your router array), you'd essentially be saying "Go to the domain route, then go to this address".
Each request should be handled in separated functions because each request has its own logic. However if you want
function request(req, res, next) {
// Your logic
}
router.post('/contact_form', request) {
// ...
}
router.post('/route1/contact_form', request) {
// ...
}
Right now, I don't have a way to test this code, but I think that will help you.
Here is yet another potential solution - use an independent function as a route handler.
router.post('/a', handlePost);
router.post('/b', handlePost);
router.post('/c', handlePost);
function handlePost(req, res, next){
// use req.path here to figure out what url was called
}

Express.js routing with optional param?

I have two situations to get data from DB
To show normal data
http://exampleapp.com/task/{{taskId}}
To edit data via posting
http://exampleapp.com/task/{{taskId}}/?state={{app.state}}
Both url have the same http://exampleapp.com/task/{{taskId}} just a little bit different with last phrase ?state={{app.state}}
I use Express routing as followed:
app.get('/task/:taskId/(?state=:status(pending|cancel|confirmed|deleted))?', routes.task.show);
But I dont know why it does not work ?
For example error: Cannot GET /task/51d2c53f329b8e0000000001 when going to h**p://exampleapp.com/task/51d2c53f329b8e0000000001
Query strings cannot be defined in routes. You access query string parameters from req.query.
app.get('/task/:taskId', function(req, res) {
if (req.query.state == 'pending') { ... }
});
However, if you're modifying a task, this is not the appropriate way to do it. GET requests SHOULD be idempotent: the request SHOULD NOT modify state. That's what POST requests are for.
app.get('/task/:taskId', function(req, res) {
// show task info based on `req.params.taskId`
});
app.post('/task/:taskId', function(req, res) {
// set task `req.params.taskId` to state `req.body.state`
});
You could either have a <form> that posts to the task, or make an ajax request:
$.post('/task/1', { state: 'pending' }, function() { ... });
According to the Express API, you cannot mix RegExp routes with string routes.
You should do something like this (I'm assuming taskId is an integer):
app.get(/^\/task/([0-9]+)/(?state=:status(pending|cancel|confirmed|deleted))?, routes.task.show);
However, I don't see why you cannot only check if req.query.state is defined in your route. It's probably less error prone and easier:
app.get("/task/:taskId", function( req, res, next ) {
if (req.query.state) {
// Do things
}
next();
});
Your problem is that query strings are not considered in routing. You will either have to redesign your urls (ie, include the state into the url itself, instead of the query string) or check the query string in your route handler function.

Handling url in express.js+node.js? accepting specific words, double slash and more

Homework:
I know few things about url handling in node.js
app.param('id', /^\d+$/);
app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
Will only accept /user/1, /user/2 ...i.e. with id as integer only
app.get('/:type(discussion|page)/:id', ...)
will only accept type with value of discussion or page
app.param('range', /^(\d+)\-(\d+)?$/);
app.get('/range/range=:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
will easily handle integer range and directly give us an array without any split or parsing and validation.
Question :
Normally server accept www.example.com/path and
www.example.com//path in same way but in node.js I have to write two
separate app.get check to do that. How can I achieve this by only
one check so that /path, //path, ///path gives same response
I have one url which looks for /:path and path can take values
listed in a dictionary
var dict={
"a":"You called a",
"b": "b is second",
"c": "cats are all over internet"
}
app.get('/:charc',function(req,res){
res.send(dict[charc]);
});
How can I restrict app to accept only a,b,c without putting an if else condition.
currently I am doing
if (typeof dict[charc] == 'undefined') res.send(404, 'Sorry, we cannot find that!');
Can I call range parameter(from homework part) sameway after '?' like
app.get('/range?range=:range',...
with url www.example.com/range?range=123-234
Regarding #1 if there's no better solution something like app.use(function (req,res,next) { req.url = req.url.replace(/[/]+/g, '/'); next(); }); would make sure no subsequent routes would see any duplicate slashes. -#Andreas Hultgren
Regarding #3, I'm pretty sure it's not possible to match query strings with express' router. Can't find a source that confirms this though. -#Andreas Hultgren
Followup to #Gaurav's comment for #1: Instead of changing the url in the route and continuing to handle the request, it ought to trigger a 3xx redirect so that the offending request is forwarded to the canonical variant. (This way the proper URL is updated in the browser. Or whatever type of client making the request is made aware that the original double-slash URL is technically invalid.)

Resources