4

I have a very basic RESTful service written using the MEAN stack (MongoDb, Express.js, Angular.js, Node.js) and utilizing the Mongoose ODM.

Product schema

var productSchema = new mongoose.Schema({
  name: {type: String, required: true, maxlength: 250},
  _prices: [{
    amount: {type: Number,required: true,min: 0.0},
    date: {type: Date, required: true, default: Date.now}
  }]
});

productSchema
.virtual('price')
.get(function () {
  var price = this._prices[this._prices.length - 1];
  return price !== undefined ? price.amount : undefined;
});

productSchema.methods.setPrice = function (amount) {
  this._prices.push({amount: amount});  
};

The purpose of the _prices array and the price virtual property is that I want to save an audit trail of the price history.

I have a RESTful interface for /products supporting the usual GET, POST, PUT and DELETE.

In my system, administrators are allowed to POST, PUT, and DELETE, which is obvious, however the issue arises for anonymous access. Anonymous users are allowed to GET only, but the GET response should not include the _prices field, as only administrators can see the history.

As anonymous

{
  name: "Widget", 
  price: "5.99"
}

As administrator

{
  name: "Widget", 
  price: "5.99", 
  _prices: [
    {price: "7.25", date: "2015-02-28"}, 
    {price: "5.99", date: "2016-01-09"}
  ]
}

The majority of tutorials I read regarding the MEAN stack always show returning the mongoose model directly.

exports.getAll = function(request, response) {
  Product.find({}, '-_prices', function (err, products) {
    res.json(200, products);
  });
};

This is very simple to get around from the products controller, however, I find this to be very misplaced as this filtering logic is in the controller, and brittle in that if I add new fields to my schema, I must go back to the controller to ensure I am not exposing fields in the future.

I also have questions about going the other way, doing request validation. It seems most tutorials have you accept user input as-is and let the mongoose validation fail.

exports.post = function(request, response) {
  var product = new Product(request.body)

  product.save(function(error, product) {
    if (error) {
      return response.json(422, error);
    }

    response.send(201);
  });  
};

Having been used to ASP.NET WebApi, my views on this are slightly skewed. I would have probably created separate view models for the request, and different view models for the response (depending on whether I'm authenticated or not), do the input validation on the request view model, and have only invariant validation within my entity. However, I want to ensure I am doing thing the MEAN stack way.

My questions are as follows:

  1. Should I rely solely on mongoose's validation for input validation, or should I write my own before it even gets to mongoose?

  2. For output filtering, should I create a security / filtering module which accepts a Product and returns a view-model depending on the user's context, dependent on whether they're an administrator or not?

  3. Should I override the toJSON behavior (which is extensible in mongoose, by the way) to do the filtering?

  4. Should I create a layer of abstraction between my controllers and mongoose?

My initial thought is that mongoose itself has too much responsibility, but I don't want to avoid using it if it's the best approach.

3
  • 2
    My first thought: Retrieving the price history/audit trail in the same request as retrieving the current price seems odd. Would it be simpler to have an admin-only /producthistory resource for this sort of thing?
    – Ixrec
    Jan 10 '16 at 12:33
  • This is a good point for this particular use case, however I find myself in the same scenario for other resources, for example employee with a field salary.
    – Matthew
    Jan 10 '16 at 16:11
  • It sounds like a good answer for employee.salary too. Is there a reason that to limit the number of different resources that access an entity? Jan 18 '16 at 14:43
1
+50
  1. Should I rely solely on mongoose's validation for input validation, or should I write my own before it even gets to mongoose?

I think that kind of checks should be done by your service, between DB and HTTP modules, not inside DB.

  1. For output filtering, should I create a security / filtering module which accepts a Product and returns a view-model depending on the user's context, dependent on whether they're an administrator or not?

I dislike the way something is returned from model to HTTP interface directly, but you can just use simplest method here: check the rights in request handlers and tweak the object by removing extra fields, if needed. This will take less lines.

  1. Should I override the toJSON behavior (which is extensible in mongoose, by the way) to do the filtering?

No.

  1. Should I create a layer of abstraction between my controllers and mongoose?

Do you mean data model layer? No. Let your controllers "control" this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.