Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js, Express, Mongoose - input validation - within route or model?

I have a rest api resource that accepts a JSON post. Example:

{
"location": {
    "coordinates": [
        -122.41941550000001,
        37.7749295
    ]
}

The coordinates are then collected from the request by Express:

module.exports.create = function(req, res, next) {

    var coordinates = req.body.location.coordinates;
    ....

These are then submitted to a Mongoose model. I am writing tests against this where the location.coordinates is missing e.g.

{
"foo": {
    "bar": [
        -122.41941550000001,
        37.7749295
    ]
}

This then fails within the validation section of the Model with :

locationSchema.path('location.coordinates').validate(function(coordinates){
                                            ^
TypeError: Cannot call method 'validate' of undefined

So my question is how would I validate that the input is correct? Should this be done in the route before getting to the model, or should it be done in the model? Any examples of how would also be appreciated.

For reference the Mongoose model looks something like:

var locationSchema = new Schema({
    userid: { type: Number, required: true },
    location: {
        type: [{
            type: "String",
            required: true,
            enum: ['Point', 'LineString', 'Polygon'],
            default: 'Point'
        }], required: true,
        coordinates: { type: [Number], required:true }
    },
    create_date: { type: Date, default: Date.now }
});


locationSchema.path('location.coordinates').validate(function(coordinates){
    ...
}, 'Invalid latitude or longitude.');
like image 292
Ben Avatar asked Oct 08 '14 22:10

Ben


1 Answers

My typical approach is to introduce a service layer in between the routes and the model, and that's where the validation happens. Don't think "service" in the "web service" sense; it simply provides an abstraction level around a given domain. This has the following benefits:

  • It gives you a common abstraction for dealing with persisted and/or external data. That is, whether you're interacting with data from Mongoose or an external web service, all of your route logic can simply interact with a consistent interface.
  • It provides sound encapsulation around persistence details, allowing you to swap out the implementation without effecting all of your routes.
  • It allows you to re-use code with non-route consumers (such as an integration test suite).
  • It provides a good layer for mocking (for use with unit tests, for example).
  • It provides a very clear "validation and business logic happens here" layer, even when your data is spread across several different databases and/or backend systems.

Here's a simplified example of what that might look like:

location-service.js

var locationService = module.exports = {};

locationService.saveCoordinates = function saveCoordinates(coords, cb) {
    if (!isValidCoordinates(coords)) {
        // your failed validation response can be whatever you want, but I
        // like to reserve actual `Error` responses for true runtime errors.
        // the result here should be something your client-side logic can
        // easily consume and display to the user.
        return cb(null, {
            success: false,
            reason: 'validation',
            validationError: { /* something useful to the end user here */ }
        });
    }

    yourLocationModel.save(coords, function(err) {
        if (err) return cb(err);

        cb(null, { success: true });
    });
};

some-route-file.js

app.post('/coordinates', function(req, res, next) {
    var coordinates = req.body.location.coordinates;

    locationService.saveCoordinates(coordinates, function(err, result) {
        if (err) return next(err);

        if (!result.success) {
            // check result.reason, handle validation logic, etc.
        } else {
            // woohoo, send a 201 or whatever you need to do
        }
    });
});

I've applied this structure to 3 or 4 different web apps and APIs at this point, and have grown quite fond of it.

like image 129
jmar777 Avatar answered Nov 15 '22 11:11

jmar777