mariogl
mariogl

Reputation: 1320

Update dynamic field in Mongoose schema

I have a simple application that communicates with an API build with NodeJS+Express+MongoDB (mongoose). My question is referred to the API side.

I use this schema to store user configuration in MongoDB:

{
    user: Number,
        name: String,
        lists: {
            invoices: {
                year: Number,
                orderCol: String,
                order: Number
            }
        }  
    }
}

There is only one document per user, that stores values used by my frontend app to show a list of invoices sorted and filtered by year.

I need to update the document, but it has to update only one property each time. For example, with a call to API I want to be able to change user.lists.invoices.year to 2017, without changing order&orderCol.

I'm sending three values to the API: type of list (invoices), property to change (year) and value to set (2017).

// PUT request with this payload
{ 
    list: 'invoices',
    property: 'year',
    value: 2017
}

I'm using user field to query.

router.put('/lists/:usr', (req, res, next) => {
    var listData = req.body;  // listData.list, listData.property, listData.year
    var usr = req.params.usr;
    Config.findOneAndUpdate(
        { user: usr }, 
        { $set: { ??? } },
        { new: true, upsert: true },
        (err, config) => {
            if (err) return next(err);
            res.json({ data: config });
        }
    )
}) 

Note for possible dupplicates: my searches in Google and SO always lead me to $ operator, but I don't know if it's the solution for this, and in any case I don't know how to use it in this case.

Maybe it's just I'm not designing the right Schema, in which case I'd aprecciate any advice.

Upvotes: 1

Views: 2070

Answers (1)

hainq
hainq

Reputation: 791

I'm not so sure about the whole API structure but I think we can solve this specific problem by the following code:

Config.findOne({ user: usr }, (err, config) => {
  if (err) return next(err);
  // Be careful if config.lists[list] or config.lists[list][property] can be undefined 
  config.lists[list][property] = value;
  // this might be needed
  // config.markModified('lists.' + list + '.' + property);
  config.save(err => {
    if (err) return next(err);
    res.json({ data: config });
  });
});

We can always find the document(s) first, apply some modifications directly before saving it again into the database instead of using the update functions.

Also, this kind of update API seems a bit unusual and I won't be able to tell if it works well for you or not. Regardless, I would suggest adding a validation middleware. Something as simple as this may help:

function validator(req, res, next) {

  const fields = ['list', 'property', 'value'];
  if (!fields.every(field => req.body[field] !== undefined)) {
    return res.status(400).json({ error: 1, msg: fields.join(', ') + ' are required' });
  }

  if (['invoices', 'interests', 'etc'].indexOf(req.body.list) === -1) {
    return res.status(400).json({ error: 1, msg: 'list is invalid' });
  }

  // More validations here...

  next();
}

The example above won't scale very well (probably should write a more general validator middleware to be shared among APIs) but hopefully it can still provide a helpful idea.

Upvotes: 2

Related Questions