We use backbone.js under node.js

Greetings, dear readers of Habrahabr. I want to share with you my experience of using backbone.js under node.js . Earlier, I actively worked with backbone.js on the client, and this library turned out to be extremely convenient for structuring code.

A service without working with any database is not a service. In my case, mongodb was chosen as the DBMS . I looked at the existing ORM solutions for mongodb, and it seemed to me more convenient to use familiar tools, especially since they will also be used on the client, so I decided to try using the Backbone.Model class for models and at the same time check how all this can be customized for mongodb.


So the challenge. There is a certain service using the mongodb base. It is necessary to be able to create new objects and save them to the database, to get the necessary objects from a certain collection. After changing the object, it should be possible to save and delete it from the database.

Backbone.js provides us with 2 basic objects - a model and a collection. We will write a layer from which we will continue to inherit all our future classes. I’ll immediately make a reservation that we will leave aside the code for connecting to the database and receiving the collection and will assume that everything is fine with it. We also neglect error handling. Almost everywhere I use Deferred. Therefore, who does not know what kind of animal it is, you can read about it here in detail. Requests to mongodb are made through thisnode.js module - which is not fundamental at all. The code is simplified in places, so if you use it in your project, something may not be entirely true.

Base model

We will need to somehow get a single model from the database, I think this method fits well into the static part of the class:
var BaseModel = Backbone.Model.extend({
        idAttribute: "_id",
    },{
        db_collection: null,    //какую коллекцию использует данная модель
        fetchFromDb: function(filter, options){
            var deferred = new Deferred();
            if (typeof options === 'undefined')
                options = {};
            db.getCollection(this.db_collection, function(err, collection){
                collection.find(filter).toArray(function(err, items) {
                        ret = new this(_.defaults(items[0], options));
                    }
                    deferred.resolve(ret);
                }.bind(this));
            );
            return deferred.promise;
        }
});

It can also be seen from the code that we have redefined the base field of the Backbone.Model class, which defines the name of the key field in the list of fields of the object, and in the static field db_collection of the class we defined the collection to which the object will belong.

After that, if, for example, we define a user class:
var User = BaseModel.extend({},{ db_collection:"users"});

we can get a specific user, for example, like this:
User.fetchFromDb({_id:1});

Now you need to learn how to save the created model. To do this, backbone.js provides a special sync method, the signature of which is as follows:
function sync(method, model, options)

Where:
  • method - the save method, depending on the situation, may be returned:
    • create - create a new model in the database;
    • update - save the data of the existing model in the database;
    • read - update the existing model from the database;
    • delete - delete the object from the database.

  • model is our object to save;
  • options - what we passed with additional parameters at the time of saving.

sync: function(method, model, options){
            var fltr = {};
            var deferred = new Deferred();
            db.getCollection(this.constructor.db_collection, collectionReady.bind(this));
            return deferred.promise;
            function collectionReady(err, collection){
                function done(err, result){
                    if (options.success)
                        options.success(result); //таким образом мы сообщаем backbone.js об успешно выполненной операции
                    deferred.resolve({context: this, result: true});
                }
                fltr[this.idAttribute] = this.get(this.idAttribute);
                switch (method){
                    case "update":
                        collection.update(
                                        fltr,
                                        this.toJSON(),
                                        {multi:false, upsert: true},
                                        done.bind(this)
                                    );
                        break;
                    case "create":
                        collection.insert(
                                    this.toJSON(),
                                    {safe:true},
                                    done.bind(this)
                                );
                        break;
                    case "read":
                        collection.find(fltr).toArray(function(err, items) {
                                done.call(this, false, items[0]);
                            }
                        }.bind(this));
                        break;
                    case "delete":
                        collection.findAndModify(fltr, [], {}, {remove:true}, function(err, items) {
                            deferred.resolve();
                        });
                }
            }
        },

Nothing complicated here either. For each specific method of changing the model, we hang up handlers who can do this action in the database itself.

Now, after creating the user object, for example, like this:
var user = new User({name:"Danil"});

we can safely save it:
user.save()

and also delete after saving:
user.delete()

Base collection

Working with single models is boring enough, so we implement collections, especially the base class in backbone.js is for this. By analogy with the model, we redefine the base class of the collection. All our future collections will be inherited from him. To begin with, we write the extraction of the list of "raw data" from the database:
var BaseCollection = Backbone.Collection.extend({
    },{
        db_collection: null,
        __fetchCollection:function(filter, collection){
            var deferred = new Deferred();
            function collectionReady(err, collection){
                collection.find(filter).toArray(function(err, items) {
                    deferred.resolve(items);
                }.bind(this));
            }
            db.getCollection(collection_db, collectionReady, this);
            return deferred.promise;
        }
});

Here we create a static class method that will allow us to retrieve a list of "raw data" from the database, and also add a static db_collection field, which the heirs will override, indicating which collection in mongodb this class belongs to.

It remains to add the initialization of the necessary models, we add another static method to our base collection class:
fetchFromDb:function(filter){
            var deferred = new Deferred();
            this.__fetchCollection(filter, this.db_collection).then(
                                    function(models){
                                        deferred.resolve(new this(models));
                                    }.bind(this)
                                );
            return deferred.promise;
        },

Here we just get the "raw data" and initialize the models associated with this collection, data from the database. Of course, we can get the name of the collection in mongodb from a related model, but we will consider this a simplification.

Now, having defined the collection of users, we can make queries to the database, receiving objects with users:
var Users = BaseCollection.extend({model:User}, {db_collection: "users"});
Users.fetchFromDb({name:"Danil"}).then(function(users){
    _.each(users,function(user){console.log(user.id)});
});


As a result, we got all the necessary minimum for working with mongodb and then we can use other opportunities that backbone.js gives us.

Also popular now: