Trouble with PUT request using Node.js (express), Angular, and MongoDB

Ben picture Ben · Aug 20, 2014 · Viewed 14.1k times · Source

I'm stuck trying to figure out how to update my data to MongoDB (I'm using Monk with MongoDB, Node, Express, and Angular). I have no trouble with POST and DELETE, but can't figure out where I'm going wrong with PUT.

Error I get:

PUT http://localhost:3000/api/habits/[object%20Object] 500 (Internal Server Error) 

Where I'm confused is with what I should be sending with Angular, and how to receive it in Node, since I understand I need to send the updated object for the record, but then how should I be accessing the ID to look it up in Mongo?

For example with my POST (more code context below), it makes sense that I'm sending the object "newHabitData"

$http.post('http://localhost:3000/api/habits', newHabitData)

But with PUT examples I see, they send the object (named id) as a :parameter

app.put('/api/articles/:id', function (req, res){

I've been using these tutorial which is the closest I could find to understanding how to use PUT: http://aleksandrov.ws/2013/09/12/restful-api-with-nodejs-plus-mongodb/ http://scotch.io/tutorials/javascript/build-a-restful-api-using-node-and-express-4

A million thanks for some advice!

ANGULAR:

// Works Fine
$scope.addHabit = function(){
    var newTitle = $scope.newTitle.trim();
    if(!newTitle){
        return;
    }
    var newHabitData = {
        title: newTitle,
        count: 0,
        status: 'active'
    };

    $http.post('http://localhost:3000/api/habits', newHabitData)
        .success(function(data) {
            $scope.habitList = data;
            console.log(data);
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });

    $('.habit-form__input-title').val('').focus();
    $scope.newTitle = '';

}

// Works Fine
$scope.habitDestroy = function (id) {       
    $http.delete('/api/habits/' + id._id)
        .success(function(data) {
            $scope.habitList = data;
            console.log(data);
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });
};

// NOT WORKING - I WANT TO INCREMENT THE COUNT, THEN SAVE
$scope.habitCheckIn = function(id){             
    id.count = ++id.count;

    $http.put('/api/habits/' + id)  // Should I be sending the entire object? Seems to make most sense to me
        .success(function(data) {
            $scope.habitList = data;
            console.log(data);
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });
}

NODE:

// Get all habits -- WORKS FINE
router.get('/api/habits', function(req, res) {
    var db = req.db;
    var collection = db.get('habits');
    collection.find({},{},function(e,data){
        res.json(data);
    });
});

// Create one habit -- WORKS FINE
router.post('/api/habits', function(req, res) {
    var db = req.db;
    var collection = db.get('habits');
    collection.find({},{},function(e,data){     
        collection.insert({ 
            title: req.body.title, 
            count: req.body.count  
        }, function(err, habit){
            if(err){
                res.send(err);
            }

            collection.find({},{},function(e,data){
                res.json(data);
            });
        });
    });
});

// Delete one habit -- WORKS FINE
router.delete('/api/habits/:habit_id', function(req, res){
    var db = req.db;
    var collection = db.get('habits');

    collection.find({},{},function(e,data){ 
        collection.remove({
            _id : req.params.habit_id
        }, function(err, todo){
            if(err){
                res.send(err);
            }

            collection.find({},{},function(e,data){
                res.json(data);
            });
        })
    })
})

// Update Habit -- NOT WORKING    
router.put('/api/habits/:habit_id', function(req, rest){
    var db = req.db;
    var collection = db.get('habits');

    // the real ID is _id, so should I be doing req.params.habit_id._id ? Or is findById asking for the entire object, and it finds the actual id itself? This is where I'm lost, can't figure it out.
    collection.findById(req.params.habit_id, function(e,data){  
        if(e){ res.send(e); }
        data.title = req.body.title;
        data.count = req.body.count;
        data.save(function(err){
            if(err){
                res.send(err);
            res.json(data);
            }
        })
    })
})

UPDATE - THE CODE BELOW GOT IT TO WORK. Thanks to saintedlama for getting me in the right direction.

Not sure if this is the right way to do it, but it works. I followed my POST format by accessing the req.body values, not the id passed in as a parameter.

So instead of this:

router.put('/api/habits/:habit_id', function(req, res) {

I did this:

router.put('/api/habits', function(req, res) {

Then I accessed the id and the updated count/title using the req.body._id and req.body.count values

router.put('/api/habits', function(req, res) {
    var db = req.db;
    var collection = db.get('habits');
    var id = req.body._id;

    if(!req.body) { 
        return res.send(400); 
    } // 6

    collection.findById(id, function(e,data){  
        if(e) { 
            return res.send(500, e); 
        } // 1, 2

        if(!data) { 
            return res.send(404); 
        } // 3

        var update = { 
            title : req.body.title, 
            count : req.body.count 
        }; // 4

        collection.updateById(id, update, function(err) { // 5
            if(err) {
                return res.send(500, err);
            }

        });
    });
});

Answer

saintedlama picture saintedlama · Aug 20, 2014

The code fragment to update existing habits should work just fine with some small corrections

  1. When running in an error always use return
  2. Always send a status back. In this case we'll send 500
  3. Check for not found and return 404
  4. Use simple update object. This will only update fields contained in the update object
  5. The mongodb document returned by monk has no save function, the document needs to be updated via the collection
  6. Check if the request has a body and send 400 - Bad Request

You can pass a object id as hex or ObjectId to findById as stated in the Monk docs.

router.put('/api/habits/:habit_id', function(req, rest){
    var db = req.db;
    var collection = db.get('habits');

    if(!req.body) { return res.send(400); } // 6

    collection.findById(req.params.habit_id, function(e,data){  
        if(e) { return res.send(500, e); } // 1, 2

        if(!data) { return res.send(404); } // 3

        var update = { title : req.body.title, count : req.body.count }; // 4

        collection.updateById(req.params.habit_id, update, function(err) { // 5
            if(err) {
                return res.send(500, err);
            }

            res.json(data);
        });
    });
});

The code above can be further simplified by using the findAndModify function of Monk.