GUIPSS

👉 Articles tagged : ORM

Choosing an ORM for node ? (Sequelize / Waterline / Bookshelf)

Posted on: 2018-03-11

Having tested those three ORMs recently, here are the elements that helped me made my choice. Hope it will help you too 😉.

Migrations / Tables creation

  • Waterline: No built-in migration system. Tables are created automaticaly. There 2 settings though: one where the tables are created based on the model's definitions (the tables are re-created when the models change), the second (called "safe") where nothing is done automatically. This is the mode you want to use in production.
  • Sequelize : It has a built-in migration system (up, down, create...). There is also a mode where tables are created automatically but it's safer than waterline's: tables are created only if they do not exist (you can force sequelize to re-create tables when your models change, therefore making it work like waterline).
  • Bookshelf : Bookshelf is based on the knex query builder so if you want to create migrations for your models you can just use knex. Its a great library, but you will have to write the migration manually. Better than waterline but more time consuming than Sequelize.

✅ Sequelize wins

Models definitions

For waterline, we have something like :

const project = {

  tableName : `project`,

  // ...

  attributes : {
    id : {
      type: `string`,
      uuidv4      : true,
      primaryKey: true,
      defaultsTo  : () => uuid.v4()
    },

    // ...

    team : {
      model : `team`,
      required : true
    },
  }
};

For Sequelize :

const Project = sequelize.define(`project`, {
    uid : {
      type: Sequelize.UUID,
      primaryKey: true,
      defaultValue: Sequelize.UUIDV4,
    },
  }, {
    // ...
  }
);

Project.belongsTo(Team);

And for Bookshelf, you don't have to list the attributes and their types :

const Project = bookshelf.Model.extend({
  tableName: 'project',
  team () {
    return this.belongsTo(Team, `team_id`, `id`);
  }
});

Bookshelf seems to win on that point, the model declaration is short and straightforward. Sequelize is the most verbose.

✅ Bookshelf wins

Joins

With Waterline, we can defin joins pretty easily :

waterline.collections.project.find({ name : 'my project'}).populate('team');

But we can not retrieve nested relations, wich is possible with Sequelize :

Project.find({
  where : { name : 'my project' }
  include : [
    {
      model : Team,
      as : 'team',
      include : [
        {
          model : User,
          as : 'users',
        }
      ],
    },
  ],
  });

It seems to be possible with Bookshelf also (though I have to admit I didn't try personnaly) :

Project.fetch({withRelated: ['team.users'])

✅ Bookshelf wins (Sequelize close second)

Performances

Check out this little test I made on github. Seems waterline is really behind Bookshelf and Sequelize in terms of performances.

✅ Bookshelf and Sequelize win

conclusion

I would definitely not choose Waterline but both Sequelize and Bookshelf seem to be good choices. Sequelize is a bit more verbose but also it's older and might be more mature. Bookshelf is simpler yet still very powerfull and uses knex, so that means it may also be easy for some critical part of your application to build you own queries.

Anyway, writing this article also convinced me that sometimes, when you are are really concerned about perfomances and having control on what you are doing, a good query builder like knex instead of an ORM is really something to consider. 🤔


Mongoose : save nested object on instance

Posted on: 2017-11-26
Categories: databases - ORM

Say you have the following schema in mongoose :

const schema = new mongoose.Schema({
  name: String,
  price : Number,
  features : Object
});

If you have created an instance from model and you try to update it, be aware of this :

const product = await Product.findOne({ name : "Fidget Spinner" });

product.features.coolColor = true;
await product.save(); // changes on features object wont be saved !

// you have to tell mongoose to save the changes :
product.markModified('features');
// and then save :
await product.save(); // changes on features object are saved :-)

This is because mongoose can't detect changes on an mixed type attribute.


Migrations examples with node-db-migrate

Posted on: 2017-01-02

Create / Remove Table


exports.up = db =>
  db.createTable(`book`, {
    columns : {
      id : {
        type       : `text`,
        primaryKey : true,
      },
      title : {
        type: `text`,
      },
      createdAt : {
        type : `datetime`,
      },
      updatedAt : {
        type : `datetime`,
      },
    },
  })
;

exports.down = db =>
  db.dropTable(`book`)
;

Add Column / Remove Column


exports.up = db =>
  db.addColumn(`user`, `node`, { type : `text` })
;

exports.down = db =>
  db.removeColumn(`user`, `node`)
;