Search code examples
node.jsmongodbvalidationbackbone.jserror-handling

Backbone, Node, and Mongo error validation and communication


I am attempting to tackle the following two problems:

  1. On model.save() make sure the entry is not a duplicate of an existing in Mongo. If it is, display UI error.
  2. Before model.save() do validation on field inputs to make sure theres no funny business like blank fields + display UI error.

First the set up.

Mongo Schema via Node.

// setup Server, include mongoose, connect to db

var List = new Schema({
    titleKey: {
        type: String,
        lowercase: true,
        trim: true,
        index: {
            required: true,
            unique: true,
            dropDups: true
        }
    },
    title: String
});

API endpoint for saving a list

app.post('/api/lists', function(req, res){
    var list = new ListModel({
        titleKey: generateTitleKey(req.body.title),
        title: req.body.title,
    });

    return list.save(function(err){
        if(!err) {
            console.log('saved list');
            return res.send(JSON.stringify(list));
        } else {
            console.log(err);
            return res.send(JSON.stringify({
                err: true,
                errSrc: "list",
                errType: "insert",
                errMsg: "That's already a list!"
            }));
        }
    });
});

and finally, Backbone .save() method for the models View

save: function() {

    /* this is called from a lightbox that might be creating a new list, or editing an existing one, so there may already be a model pre-loaded into this point. */

    var _self = this;
        newTitle = _self.$el.find('input[name="new-list-name"]').val();

    // create model
    _self.opts.model = new app.AchievementList({
        title: newTitle,
    });

    _self.opts.model.save(null, {
        wait: true,
        success: function(model, res) {
            new app.AchievementListView({ model: _self.opts.model });
        },
        error: function(model, error) {}
    });
},

So here is where I have a strategy meltdown.

Backbones .save({ success / error }) don't behave as expected when listening for Mongo error outputs

Ideally I wanted Mongo to do the duplicate error handling. It already detects duplicates and throws an error so why not. But then I discover that ANY reply from the API is treated like a success: response in the .save() method, so the view would get rendered even if the error is present because technically success is fired. Do I do an error check inside success:? It seems like that's what the error: is for but then I'd be throwing around server error responses (like 501) to force the error: handler, and 501's show up in the console. That does't seem right, It's not that "I couldn't reach the server" at all.

The alternative I thought of is to run a wrapping $.ajax(GET) request, looking for a duplicate entry and then acting accordingly. But if I'm doing that then why bother asking Mongo to prevent duplicates? I would never submit one that is a copy and if I did I wouldn't know about it, theres nothing to catch that error. Feels like Im missing out on Mongos powerful error handling, and putting up all sorts of random $.ajax calls and API lookups any time I need anything from the server. Not good...

Backbones model.validate() method isn't ideal to check for an input error and simply break saving flow

But OK, lets say I DO parse the response in success: and prevent view.render(). Fine. When I go to validate the input fields before submission I hit yet another wall. Backbones validate method does a weird thing where you have to listen to the model's "invalid" change. Problem is the part that runs model.save() wont always have a model in the view when it's initialized so I can't bind a listener to something that might not be there. So the common thing to do is just check the fields manually with a generic if else check, but that doesn't seem backbone-like. The whole point of MV is that models should handle their own errors and such, so if I'm validating outside of them wherever it's convenient... something just doesn't seem right about this. Neither does forcing error checks in save({ success: }).

I know this is technically a lot of complaining and whining but the whole point of me learning these things is that I wanna know how to do them right. I know all 3 frameworks mentioned here have powerful tools to handle common needs like validation so I can't help but feel like I'm missing some common sense approaches to these typical problems. If anyone can give me any insight on either of these problems, much appreciated. It's been a real pain trying to solve them simultaneously.


Solution

  • The problem here is your API endpoint is returning 200 (SUCCESS) regardless of the error state in the server. It should return the appropriate error code that match the error type, in order for backbone to intercept the error. For instance, for duplicated records, its common practice to return 409 conflict (see HTTP codes list). Assuming you're using express with node.js, you can send the status this way, before you send the response:

    ...
    res.status(409);
    ...
    return res.send(...
    

    Then, on your backbone model, implement the error listener as part of your model events:

    MyModel = Backbone.Model.extend({
      ...
      events: {
       'error': 'errorHandler',
      },
      ...
      errorHandler: function(error) {
        // do something with that error
      }
    });
    

    Your model can then send event that your view will capture in order to display the appropriate error message to the user.