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:
Should I rely solely on mongoose's validation for input validation, or should I write my own before it even gets to mongoose?
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?Should I override the
toJSON
behavior (which is extensible in mongoose, by the way) to do the filtering?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.
/producthistory
resource for this sort of thing?employee
with a fieldsalary
.