joe joe - 5 months ago 25
Node.js Question

Am I doing this API Put request correct with mongoose?

So I'm using mongoose and mongodb and express routing to do it.

I have a user document with an empty array on creation- "todolist", I want to be able to edit this to add a task to the list as well as edit those tasks.

Right now I'm thinking the best way to do it is have the server check if the req.body has certain variables set to know which part of the document I need to edit.

Is this the correct approach or am I suppose to create a new API route for something like this or am I doing it correctly by checking req variables from the client to determine what I want to edit on the server? Is there a cleaner way to do this too?

router.put('/:user_id', function(req, res) {
User.findById(req.params.user_id, function(err, user){
if(err){
res.send(err);
return err;
}

if(req.body.createTask) {
user.todolist.push({
"task_name": req.body.task_name,
"task_importance": req.body.importance,
"task_completed": false
})
}else if(req.body.edit_task){
if(req.body.edit_task_name) {
user.todolist[req.body.index].task_name = req.body.new_task_name;
}else if(req.body.edit_task_importance) {
user.todolist[req.body.index].task_importance = req.body.new_task_importance;
}else if(req.body.edit_task_completed) {
user.todolist[req.body.index].task_completed = true;
}
}

user.save(function(err){
if(err)
res.send(err);
res.json({message: 'User updated'});
})
})
});

Answer

I would recommend you to model your database with proper entities, creating a Task model separately from User and adding a reference to User, like this:

var TaskSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.ObjectId,
    ref: 'User'
  },
  name: String,
  importance: String,
  completed: Boolean
});

If you want to, you can also add reference to Task on User:

var User = new mongoose.Schema({
  // note this is an array
  todolist: [{
    type: mongoose.Schema.ObjectId,
    ref: 'Task'
  }]
});

Doing that, to make things simpler, you should create a pre save hook to automatically add tasks to user:

TaskSchema.pre('save', function(next) {
  if (this.isModified('user') && this.user) {
    // $addToSet will add this document to the user's todolist (if it's not already there)
    User.findOneAndUpdate({_id: this.user}, {$addToSet: {todolist: this}}, function(err, doc) {
      next(err);
    })
  } else {
    next();
  }
});

This way the correct approach would be provide endpoints correlated to entities. So, to create a task, a user should request POST /tasks with body { user: '123', name: 'name', ... }:

router.post('/tasks', function(req, res) {
  Task.create(req.body, function(err, task) {
    if (err) return res.status(500).send(err);
    return res.status(201).json(task);
  });
});

It will automatically add the task to the user 123 with our pre save hook. To edit the task, just make that on task endpoint:

router.put('/tasks/:id', function(req, res) {
  Task.findById(req.params.id, function(err, task) {
    if (err) return res.status(500).send(err);
    if (!task) return res.status(404).send('Not Found');

    task.name = req.body.name || task.name;
    task.importance = req.body.importance || task.importance;
    task.completed = true;

    task.save(function(err, task) {
      if (err) return res.status(500).send(err);
      res.status(200).json(task);
    });
  });
});

You don't need to edit user as it only keeps tasks reference.

If you want to get users with tasks data populated (and not only their ids), just do:

router.get('/users', function(req, res) {
  User.find({}).populate('todolist').exec(function(err, users) {
    ...
  });
});

For queries like that above, I recommend you to use some library like querymen, which parses querystrings (e.g. GET /users?q=Some+Search&limit=10&page=2) to MongoDB query arguments.

Hope it helps. :)