How to handle null URL parameters with Express.js - javascript

I'm working on an assignment for my web services class and can't figure out how to handle a null URL parameter. Here is my code:
app.get('/nachos/:x/:cheese/:tomatoes/:salsa/:hotsauce', (req, res) => {
var x = parseInt(req.params['x'])
var cheese = (req.params['cheese'])
var tomatoes = (req.params['tomatoes'])
var salsa = (req.params['salsa'])
var hotsauce = (req.params['hotsauce'])
res.send('You ordered ' + x + ' nacho(s) with ' + cheese + ', ' + tomatoes + ', ' + salsa + ', '
+ hotsauce + '.')})
This code works fine with all parameters filled. But how do I handle a null parameter if I don't want, for example, salsa and type in the url localhost:port/nachos/1/cheese/tomatoes/hotsauce

If I'm reading correctly the question is:
How do I handle a null parameter if I don't want, for example,
salsa and type in the url localhost:port/nachos/1/cheese/tomatoes/hotsauce
You won't get null it simply won't match the route.
You can make params optional like:
app.get('/nachos/:x?/:cheese?/:tomatoes?/:salsa?/:hotsauce?', (req, res) => {
or add routes to match, which could get out of hand.
app.get([
//...
'/nachos/:x/:cheese/:tomatoes',
'/nachos/:x/:cheese/:tomatoes/:hotsauce',
'/nachos/:x/:cheese/:tomatoes/:salsa/:hotsauce',
//...
], (req, res) => {

You don't really do optional arguments in a route definition that looks like this:
/nachos/:x/:cheese/:tomatoes/:salsa/:hotsauce
because in order for the route to match, every one of the parameters must be present and for req.params to contain the right option, they must all be in the correct order. If these are variations, some of which are entirely optional, then matching them the way you were is just not the proper URL design and matching a varying URL like that in Express will be a bit of a pain.
The simplest way I can think of is to just put the optional ingredients in a query string as in:
/nachos/2?cheese=yes&tomatoes=yes&salsa=yes&hotsauce=yes
Then, you just match the route: /nachos/:x as the only required options for the URL and everything else can be optionally specified in the querystring and you can write your code so that it defaults to "no" if the option is not present. So, an order without hotsauce could be either of these:
/nachos/2?cheese=yes&tomatoes=yes&salsa=yes&hotsauce=no
/nachos/2?cheese=yes&tomatoes=yes&salsa=yes
An order without hotsauce or salsa could just be this:
/nachos/2?cheese=yes&tomatoes=yes
Then, your request handler would look like this:
const orderOptions = ["cheese", "tomatoes", "salsa", "hotsauce"];
app.get('/nachos/:x', (req, res) => {
console.log(req.query);
let text = orderOptions.map(item => {
return req.query[item] === "yes" ? item : null;
}).filter(item => !!item).join(", ");
res.send(`You ordered ${req.params.x} nacho(s) with ${text}.`);
});
I should mention that if this request is actually specifying an order, then it should probably be a POST, not a GET and the options should be in the body of the POST and they would take the same form as the querystring, probably encoded just like application/x-www-form-urlencoded from form POSTs.

Passing parameters this way request to the API user to send the parameters in the correct order. To handle the case you want (an ingredient missing), you would need to create another route to handle it. But you can do this in another way, like passing the parameters as query strings or even sending them on the request body (for non-GET routers preferably).

Related

How to get JSON value from PHP?

I'm trying to get JSON value from PHP but if one of the property values contains a forward slash "/" it fails (I'm using GET to send data for this particular scenario).
Here's how I'm sending the data (This works just fine when I don't send "/").
UI side
const dataObj = {
description: 'my / description',
buyer: 'Mike Brown'
};
const dataString = JSON.stringify(dataObj);
fetch(`http://localhost:8000/clients/${dataString}`)
.then((response) => response.text())
.then((responseData) => {
......
});
PHP side:
Route::get('/clients/{data}', function($data) {
// This line below fails ONLY if value of description property contains "/"
// otherwise it works just fine
$dataToBeSaved = json_decode($data, true);
});
Yes, I did some research on json_decode, but nothing very clear. Can anyone point me on the right direction? Thanks a lot in advance!
It's not really a good idea to pass JSON data in a URL, and you can't do it directly because it will contain characters that have meaning in URLs (e.g. /, ?, =, etc.). But if you must you have a couple options:
You can URL encode the string and pass it as a parameter. This wouldn't work with the route you have, but it has the benefit of not needing to do anything else. You can just get the value from the parameter.
const dataObj = {
description: 'my / description',
buyer: 'Mike Brown'
};
const dataString = encodeURIComponent(JSON.stringify(dataObj));
console.log(`https://example.com/clients?data=${dataString}`);
Or you can base64 encode it. This doesn't by default create URL safe strings, so you'll have to replace a few characters to make it URL safe. That also means you'll have to do the reverse on the server. This solution will work with your route.
const base64UrlEncode = function(str) {
return btoa(str)
// this character have meaning in URLs so we need to replace them
// with something else. we'll have to reverse this on the server.
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
};
const dataObj = {
description: 'my / description',
buyer: 'Mike Brown'
};
const dataString = base64UrlEncode(JSON.stringify(dataObj));
console.log(`https://example.com/clients/${dataString}`);
And to decode on the server:
function base64UrlDecode($encodedStr) {
// undo the character replacements we did when encoding the string
$unreplace1 = str_replace('-', '+', $encodedStr);
$unreplace2 = str_replace('_', '/', $unreplace1);
return base64_decode($unreplace2);
}
Route::get('/clients/{data}', function($data) {
$dataToBeSaved = json_decode(base64UrlDecode($data), true);
});
One thing to note with this solution is that web servers usually have a limit for the length of URLs (e.g. Apache's default is 8,177 characters). Also, there is usually a limit to the size of the "filename" in the URL (the last component in the path). Apache's default is 255 bytes/characters. So if your base64 encoded JSON is longer than 255 characters, it won't work. A better solution is to pass the data as part of the request body (i.e. as a POST request). That way you won't have a limit and you won't need to encode it beyond converting it to JSON.

Cypress - cy.intercept catching wrong url

i try to mock my API routes with intercept but i don't know why it's the wrong route which triggered (i'm on cypress#6.2.1)
i have two intercepts :
one for /contacts and second /contacts/Contact-ARandomId
cy.intercept('GET', 'http://localhost:5000/contacts', {statusCode: 200, body: dataMultiple})
cy.intercept('GET', 'http://localhost:5000/contacts/Contact-ARandomId', {statusCode: 200, body: dataARandomId})
Ref Matching URL
You can provide a substring of the URL to match
// will match any request that contains "users" substring, like
// GET /users?_limit=3 and POST /users
cy.intercept('users')
so 'http://localhost:5000/contacts' matches because it is the first defined, and partial matching applies.
You could just reverse the order of the intercepts, set the more specific URL first (sort of like routes on a SPA).
Alternatively, take a look at Set an alias dynamically.
You can use javascript to refine the response
cy.intercept('GET', 'http://localhost:5000/contacts', (req) => {
const isContactById = req.url.split('/') // split into parts
.pop() // take last part
.startsWith('Contact-'); // check if has id prefix
const bodyStub = isContactById ? dataARandomId : dataMultiple;
req.reply(200, bodyStub);
})

How to pass array in the url?

I'd like to filter and want to pass an array to the url.
handleFilter = (search, page = 1) => {
const requestOption = {
method: "GET"
};
fetch("http://127.0.0.1:8000/api/home?search=" + JSON.stringify(search) + "&page=" + page, requestOption)
.then(res => res.json())
.then(data => (
this.setState({
data
})
))
};
Just want to pass the array of data to the api to call the query
I don't know if this will be what exactly you need, but I would...
a.) Join it on a strange character, and pass it as a string.
b.) On the receiving side (client or server), split on that same character.
If you want send it exactly as GET method (not Post), you can form your URL like this:
"http://127.0.0.1:8000/api/home?search[]=value1&search[]=value2&search[]=value3"
That is if you don't want send JSON string.
And what the problem with JSON version or POST method?

Pass parameter to Express controller from button click

Preface: I am writing a Web App using Node.js, Express, and MongoDB.
When a button is clicked I am trying to pass a parameter via URL from my index.ejs to the Express controller ItemController.js in order to dynamically create a filtered set of data.
The button is defined within index.ejs as follows:
<button type="button" class="btn btn-primary" value="/items/items" onclick="loadItems(value, 'Scripting Languages')">
Scripting Languages
</button>
In my external Javascripts file which houses miscellaneous functions, loadItems executes the following:
function loadItems(page, subcategory) {
window.history.replaceState({}, '', "?subcat=" + subcategory); //set URL Param
$('#mainContent').load(page); //load item listing page into grid within HTML GridLayout
}
After this, the router/controller handles the data filtering and page rendering:
Router (item.js)
...
// Get all items
router.get('/items', item.items);
...
Controller (ItemController.js)
...
// loads item listings
itemController.items = function(req, res) {
Item.find({}).exec(function (err, items) {
if (err) {
console.log("Error:", err);
}
else {
var URLSubcat = "Scripting Languages"; //this variable needs to change
var filtered = items.filter(function(el) {
return el.subcategory === URLSubcat;
});
res.render("../views/items/items", {items: filtered});
}
});
};
...
I have tried using req.query.subcat in order to try and grab the parameter from the URL, however it comes back as undefined. I have also tried the techniques in the links below to no avail. What should I be doing to accomplish this? Is there a better way to go about this?
Previously Tried
How to get GET (query string) variables in Express.js on Node.js?
How to access the GET parameters after "?" in Express?
$('#mainContent').load(page);
The URL you are requesting is stored in the page variable.
loadItems(page, subcategory)
… which is defined as an argument
onclick="loadItems(value, 'Scripting Languages')"
… which is passed from the onclick event handler
value="/items/items"
… which gets it value from that attribute.
The query string doesn't show up because the URL you are requesting does not include a query string!
The only query string you have is from here:
window.history.replaceState({}, '', "?subcat=" + subcategory);
… which modifies the URL in the browser addressbar and history without actually requesting it.

Using Slugs in Meteor's Iron Router

I'm trying to define an Iron Router route to use a slug: ie. a string title but with spaces replaced by hyphens.
Instead of /news/breaking%20news, we want it to be /news/breaking-news.
Router.map(function() {
this.route('news', {
path: '/news/:title'
})
})
To achieve this, do we need to create a new document field slug with the slug string and use path: '/news/:slug? Or is there a more elegant method that avoids this redundant field?
App is using packages aldeed:simple-schema, aldeed:collection2 and dburles:collection-helpers if those help.
The constraining issue is the 1-way, non-reversible transformation from the actual title to the slug (e.g., "foo-bar" and "foo bar" both end up with the same slug, "foo-bar", so when you see "foo-bar" you don't know what the actual title is, so you can't look it up). So unless you can control for that (e.g., adding the slug to the document as you suggest, or enforcing a strict no-hyphens policy for titles in the application), you'll need a way to store the mapping.
One potential way around this would be to include the document ID in the URL and have a meaningless slug, which can be filled in by the router itself:
Router.route('/news/:_id/:slug?', ...)
Then only use the _id in the actual routing. Note that this allows you to put whatever you want in the URL after the _id, so that 'news/[_id]/foo-bar' and 'news/_id/foo%20bar' both go to the same page, (news/[_id]), but so does 'news/[_id]/this_is_completely_meaningless.' In the router, you can then redirect to the appropriate URL as needed:
Router.route('/news/:_id/:slug?', {
name: 'news',
data: function() { return news.findOne({_id: this.params._id}); },
onBeforeAction: function() {
var data = this.data();
if (data) {
var realUrl = '/' + data._id + '/' + slugify(data.title); // the desired URL, incl. whatever your slug fn is
if (this.url.indexOf(realUrl) < 0) {
this.redirect('/news' + realUrl); // if we aren't at the desired URL, take us there
}
}
this.next();
}
});
You'd then have to control for any "share this page" functionality to ensure that it gives the desired URL, but if anyone tries to go to news/[_id]/this_is_completely_meaningless it'll redirect them to news/[_id]/foo-bar.

Categories

Resources