Restangular put() yielding a 404 when updating from a form - node.js

Restangular has been throwing me a 404 when sending PUT requests, and I'm not sure why.
I have an Events collection, and I'm getting a single event and updating it in a form.
According to the Restangular docs, when I make a PUT request on the Event, it should route to api/events/:eventId. Instead, I'm getting an error saying it's routing to api/events.
The error:
PUT http://localhost:9000/api/events 404 (Not Found)
Can you see where I'm going wrong? I haven't changed the config on Restangular at all.
Here's my controller:
var restEvent = Restangular.one("api/events", $routeParams.eventId);
restEvent.get().then(function(eventData) {
// the eventData is successfully coming through
$scope.thisEvent = Restangular.copy(eventData);
// $scope.thisEvent is modified in the form
// updateEvent() runs when my Event form is submitted
$scope.updateEvent = function() {
$scope.thisEvent.put();
};
});
Here's my Express update routes:
// update route -------------
app.route('/api/events/:event_id')
.get(events.show)
.put(events.updateEvent);
Any suggestions?

This is actually an issue with Restangular/Mongo. Restangular defaults its primary key to "id", whereas Mongo uses "_id". When Restangular tries to append "id" from the Event object to api/events, it can't find "id" and only routes to api/events.
Here's the code that the Restangular docs suggest, and that solves this issue:
app.config(function(RestangularProvider) {
RestangularProvider.setRestangularFields({
id: "_id"
});
});

It could be browser problem, I would recommend to go for this for PUT and PATCH:
app.config(function(RestangularProvider) {
RestangularProvider.setMethodOverriders(['put', 'patch']);
});

Related

Searching one collection multiple times in one call

I have an array with multiple values which need to be used as queries to search a collection. I am not sure of a way of doing this using one route. for example:
router.get('/', ensureAuthenticated, (req, res) => {
let ArrayOfIds = [id1, id2, id3]
Movie.find({user: req.user.id}).then(items => {
//Something along this lines
items.subscriptions.forEach(subscription => {
Movie.find({user: subscription})
//This wont work beacuse of the callback promise
.then(movies => {
items.push(movies)
})
})
res.render('index/home',
{pageName: 'Movies', items: items})
.catch(err => console.log(err));
});
})
I want to search the same collection (Movie) for each id and add it to the items object. Doing it with a for loop seems to create a set header error. Any ideas?
This S/O response should have your answer...
Error: Can't set headers after they are sent to the client
From the response:
The error "Error: Can't set headers after they are sent." means that
you're already in the Body or Finished state, but some function tried
to set a header or statusCode. When you see this error, try to look
for anything that tries to send a header after some of the body has
already been written. For example, look for callbacks that are
accidentally called twice, or any error that happens after the body is
sent.
In your case, you called res.redirect(), which caused the response to
become Finished. Then your code threw an error (res.req is null). and
since the error happened within your actual function(req, res, next)
(not within a callback), Connect was able to catch it and then tried
to send a 500 error page. But since the headers were already sent,
Node.js's setHeader threw the error that you saw.
It's likely that you're calling res.redirect() multiple times if you're implementing a for-loop.
Maybe if I understand correctly, you can query the collection with list of ids in your case ArrayOfIds into in query of mongo.
Check $in query of mongoDB.
FYI: create some index on that field.

Node.JS run routes synchronously

In this example, I have two routes - the first is a Get route and the second is a Post route. I want the information gathered in the get route to be included in the post route. I tried using .then and some basic boolean if logic but I cannot get these routes to run synchronously.
leadFormObj = {};
$.get("/getID/"+leadFormObj.parentEmail, function(event){
console.log("getting an ID");
console.log(event[0].id);
leadFormObj.parentID = event[0].id;
});
console.log(leadFormObj);
$.post("/addChild", leadFormObj, function(data) {
console.log(leadFormObj);
console.log("sent");
});
In the example above, I have a standard object (i've shown it blank in this example). The first get route will run and pass in a new key value pair to the object. I then want to pass this updated object to the post route but I'm not sure how to do this.
Would I use nested routes to do this?
Thanks!
Why don't you put the post request inside the callback of the get request
$.get("/getID/"+leadFormObj.parentEmail, function(data){
$.post("/addChild", {parentID: data[0].id}, function(data) {
console.log("sent");
});
});

Unable to make PUT request to Cloudant-bluemix database using Nodejs-express

I'm trying to implement a Password reset. So I'm taking the phone number of the user, getting the document from the database using the phone number to find it, and I'm taking the new password and trying to update the corresponding document using a PUT request in my Cloudant database.
app.post('/pass_rst', function(req,response){
var log='';
//log is just for me to see what's happening
var phone= req.body.phone;
log+=phone+'\n';
db.find({selector:{'phone':phone}}, function(err, result){
if(err){
throw err;
}
if(result.docs.length==0){
response.send('User doesnt exist');
}else{
var existing_data=result.docs[0];
log+=JSON.stringify(existing_data)+'\n';
var upd_pswd= req.body.new_password;
log+=upd_pswd+'\n';
var new_data=existing_data;
new_data.password=upd_pswd;
log+=JSON.stringify(new_data)+'\n';
var id= result.docs[0]._id;
log+=id+'\n';
//make PUT request to db
var options={
host:dbCredentials.host,
port:dbCredentials.port,
path:'/'+dbCredentials.dbName+'/'+id,
//url: dbCredentials.url+'/'+dbCredentials.dbName+'/'+id,
method:'PUT',
json:new_data,
headers:{
'Content-Type':'application/json',
'accept':'*/*'
}
};
log+=JSON.stringify(options)+'\n';
var httpreq= http.request(options);
//log+=JSON.stringify(httpreq);
httpreq.on('error', function(e){
response.send('Error'+e.message);
});
response.send(log+'\n\n\nUpdated');
}
});
});
dbCredentials is defined above as follows:
dbCredentials.host = vcapServices.cloudantNoSQLDB[0].credentials.host;
dbCredentials.port = vcapServices.cloudantNoSQLDB[0].credentials.port;
dbCredentials.user = vcapServices.cloudantNoSQLDB[0].credentials.username;
dbCredentials.password = vcapServices.cloudantNoSQLDB[0].credentials.password;
dbCredentials.url = vcapServices.cloudantNoSQLDB[0].credentials.url;
I've tried tinkering around with it, but in the best case scenario, I don't get an error and I see "Updated" but nothing actually happens in the database. Sometimes I get an error saying : 502 Bad Gateway: Registered endpoint failed to handle the request.
If you see what's going wrong, please let me know. Thank you.
This is the documentation on how to update documents in cloudant
UPDATE
Updating a document
PUT /$DATABASE/$DOCUMENT_ID HTTP/1.1 { "_id": "apple", "_rev":
"1-2902191555", "item": "Malus domestica", "prices": {
"Fresh Mart": 1.59,
"Price Max": 5.99,
"Apples Express": 0.79,
"Gentlefop's Shackmart": 0.49 } }
To update (or create) a document, make a PUT request with the updated
JSON content and the latest _rev value (not needed for creating new
documents) to https://$USERNAME.cloudant.com/$DATABASE/$DOCUMENT_ID.
If you fail to provide the latest _rev, Cloudant responds with a 409
error. This error prevents you overwriting data changed by other
processes. If the write quorum cannot be met, a 202 response is
returned.
Example response: { "ok":true, "id":"apple",
"rev":"2-9176459034" }
The response contains the ID and the new revision of the document or
an error message in case the update failed.
I am using bluemix -nodejs and cloudant. The best way that worked for me to do the update is to use the nano package for db interaction from node.js.
You can refer to the post here:
The summary is - By making use of nano api, you can easily update the record. You need to make sure to use - the _id and the right _rev number, while you use nano. This inturn uses PUT Method underneath.
Updating and Deleting documents using NodeJS on Cloudant DB
When you are including nano, make sure to update the package.json to have the nano dependency added. Let me know if you have further questions on the update/delete
When using the cloudant node.js module there is no separate update function.
You need to use the db.insert function also for the update with the right doc revision, so you need to read the latest revision before the insert.
https://github.com/apache/couchdb-nano#document-functions
"and also used to update an existing document, by including the _rev token in the document being saved:"
// read existing document from db
db.get(key, function(error, existing) {
if (!error)
// use revision of existing doc for new doc to update
obj._rev = existing._rev;
// call db insert
db.insert(obj, key, cb);
});

Unable to make an update on the server with an id

I am updating a form and i want to make an update request on the serverwith an id
my model is:
var CampaignEditModel = Backbone.Model.extend({
urlRoot:"http://localhost:3033/campaign/update/",
url : function(){
var url = this.urlRoot + this.id;
return url;
},
idAttribute: "_id",
defaults:{
"id":null ,
"Name" :""
}
});
render function is called here:
$contents.empty().append(new EditView({model:editCampaigns}).render({id:id}).el);
and render function is:
render: function(options){
this.$el.append( _.template(EditTemplate));
this.model.set({"id":options.id})
console.log(this.model.get("id"));
this._modelBinder.bind(this.model, this.el);
return this;
},
events: {
'click .saveCampaign ': 'save'
},
save:function(){
this.model.set({
"Name" :$('#edname').val(),
});
this.model.save(null, {success: function(data){
console.log("data:" + data);
require(['campaignroute'],function(routes){
var router = routes.pageRouter;
router.navigate('gridView', {trigger: true});
});
}});
return false;
}
the problem is even i have set an id in the model still when save method is called
the request go like this
http://localhost:3033/campaign/update/undefined
and console shows the eror:
Failed to load resource: the server responded with a status of 404 (Not Found)
how to solve this problem?
Instead of passing options to your custom render(options) function and setting the model id there, set the it directly on the editCampaigns model, before entering render(options):
editCampaigns.set('id', id);
$contents.empty().append(new EditView({model:editCampaigns}).render().el);
and remove the extra
this.model.set({"id":options.id})
from render(options) together with the options parameter. It should look like similar to this:
render: function(){
this.$el.append( _.template(EditTemplate));
console.log(this.model.get("id"));
this._modelBinder.bind(this.model, this.el);
return this;
}
Your model also has an extra url function:
url : function(){
var url = this.urlRoot + this.id;
return url;
}
you don't need this one since the models' id is automatically appended after urlRoot.
Unrelated to you problem I see you used
http://localhost:3033/campaign/update
to define your update URL. The HTTP method you use, already says what kind of action will be executed, this is the reason why you can (and should) write URLs without verbs. Just remove the extra /update.
Here is a quick summary about best-practices:
How to create REST URLs without verbs?
Double check that the request is a post request and not a put request. 'Failed to load resource' errors is usually related to a missing request handler.

How to apply post redirect and get pattern in node.js?

I am testing with a very simple application in node.js where I create and save an application. I show the post form with the newPost function and I receive the post with the data in the savePost method. In the latter one I do a validation (with iform module) and I want to go show again the same page as before but filling the form with the data sent by the user and also with the errors found.
I have a similar code like this one. In it I render the same jade page if I find any error. It works though I want to apply the pattern redirect and get there as I don't want to send again the post request when the user presses F5.
So, how is the usual way to make a post redirect and get from the post method passing them all the parameters I have received adding the errors? Is there any module which can help to do so?
var prepareObject = function(req, res){
var errors = {};
if('iform' in req){
errors = req.iform.errors;
}
return {title: 'Nuevo Post', body:req.body, errors: errors};
};
// mapped as /newPost (type GET)
exports.newPost = function(req, res){
//show form to create post
res.render('newPost', prepareObject(req, res));
}
// mapped as /savePost (type POST)
exports.savePost = function(req, res){
if(req.iform.errors) {
//there are errors: show form again to correct errors
res.render('newPost', prepareObject(req, res));
}else{
//no errors: show posts
res.redirect('/posts');
}
}
You can redirect to GET "/newPost" instead of rendering the "newPost" template.
To have autocomplete working, you may either add the data to the redirect query (faster) and render it, or add the data to the session (don't forget to delete it after rendering), but the later option requires a session store.

Resources