Getting Started with Camo

Edit: Updated code to Camo v0.12.1

Introduction

First of all, Camo is a new class-based ES6 ODM for MongoDB and Node. With mainstream ES6 quickly approaching us, I thought we were long overdue for an ODM that took advantage of the new features, so I created Camo. What bothered me the most when transitioning from Java to JavaScript programming was the lack of traditional-style classes. I think for beginners especially, this is an important feature for any ODM to have.

In this tutorial I'll show you how to use the basic features of Camo (schema declaration, saving, loading, etc). To make comparisons with Mongoose easier, I'll be showing similar examples to the article Getting Started with Mongoose. Hopefully from here you can decide which style/functionality you like the best for use in your projects.

Connecting Camo to MongoDB

Note: The code in this article uses v0.12.1 of Camo.

To connect Camo to your database, just pass the connection string (typically of the form mongodb://[ip-address]/[db-name]) to the connect() method, which will return an instance of the Camo client. The client instance can be used for configuring the DB, but isn't needed for declaring, saving, or deleting your documents.

var connect = require('camo').connect;

var database;  
var uri = 'mongodb://localhost/test';  
connect(uri).then(function(db) {  
    database = db;
});

Schemas and Models

Models are declared using ES6 classes and must extend the Document object. The model's schema is declared within the constructor, and any member variable that does not start with an underscore (_) is included in the schema and saved to the DB. A member variable can be declared by either directly assigning it a type, or by assigning it an object with options.

var Document = require('camo').Document;

class Movie extends Document {  
    constructor() {
        super();

        this.title = String;
        this.rating = {
            type: String,
            choices: ['G', 'PG', 'PG-13', 'R']
        };
        this.releaseDate = Date;
        this.hasCreditCookie = Boolean;
    }

    static collectionName() {
        return 'movies';
    }
}

The collection name is declared by passing overriding the static collectionName() method. Although this name is rarely needed in the code, it will become useful when inspecting the MongoDB database manually, so it's best to keep it relevant to the model name.

If collectionName() is not provided, then Camo will automatically assign a collection name based on the name of the class.

Create, Retrieve, Update, and Delete (CRUD)

To create an instance of the model, just use the create() method, which handles much of the instantiation work for you. Optionally, data can be passed to create() that will be assigned to the member variables. If data is not passed for a variable, it is then either assigned a default value (if specified in the schema), or assigned null.

var thor = Movie.create({  
    title: 'Thor',
    rating: 'PG-13',
    releaseDate: new Date(2011, 4, 2),
    hasCreditCookie: true
});

thor.save().then(function(t) {  
    console.log(thor);
});

When saving the instance, the data will be inserted in to the database if it has not previously been saved. If the instance has already been saved to the database, then the existing document is updated with the changes.

To delete a document, just simply call the delete method on it. You can also use the static deleteOne(), deleteMany(), or findOneAndDelete() on the model class. The number of documents deleted from the database will be returned within the Promise.

thor.delete().then(function(numDeleted) {  
    console.log(numDeleted);
});

To load a document from the database, you have a few choices depending on what you want to do. The options are:

  • findOne() for loading a single document (or null if one does not exist)
// Load only the 'Thor' movie
Movie.findOne({ title: 'Thor' }).then(function(movie) {  
    console.log(thor);
});
  • find() for loading multiple documents (or an empty array if none exist)
// Load all movies that have a credit cookie
Movie.find({ hasCreditCookie: true }).then(function(movies) {  
    console.log(thor);
});
  • findOneAndUpdate() for retrieving a document and updating it in a single atomic operation
// Update 'Thor' to have a rating of 'R'
Movie.findOneAndUpdate({ title: 'Thor' }, { rating: 'R' }).then(function(movies) {  
    console.log(thor);
});

Extras

Thanks to Camo's support of ES6 classes, we can easily define statics, virtuals, and methods for the models, which makes the model code much more organized and readable.

var Document = require('camo').Document;

class Movie extends Document {  
    constructor() {
        super();

        // Schema declaration here...
    }

    set releaseYear(year) {
        this.releaseDate.setYear(year);
    }

    get releaseYear() {
        return this.releaseDate.getFullYear();
    }

    yearsOld() {
        return new Date().getFullYear() - this.releaseDate.getFullYear();
    }

    static findAllRMovies() {
        return this.find({ rating: 'R' });
    }
}

var uri = 'nedb://memory';

connect(uri).then(function(db) {  
    var thor = Movie.create({
        title: 'The Matrix',
        rating: 'R',
        releaseDate: new Date(1999, 2, 31),
        hasCreditCookie: true
    });

    return thor.save();
}).then(function(t) {
    return Movie.findAllRMovies();
}).then(function(movies) {
    movies.forEach(function(m) {
        console.log(m.title + ': ' + m.releaseDate.getFullYear());
    });
});

In addition to interfacing with MongoDB, Camo also supports NeDB, which is like the SQLite-equivalent to Mongo. An example of using NeDB is shown above. It's very helpful to use during development and testing since data can be stored in either a file or just memory. This also means you can use Camo in front-end browser code!

Head on over to the project page for more info. Like what you see? Star the project and spread the word!