JS on Backend in 2018. Tutorial. Part 3. Relationships and Sequelize ORM tuning.

Hello, my dear, today we will dive deeper into the abyss of ORM configuration.

We will add one more resource, set a relationship between this one and the one we already had after this postย and will cover some useful tuning options. Spoiler: we will even change a bit the old one. So, fasten your seatbelts, let’s start!

Improving the previous model

Do you remember the command

node_modules/.bin/sequelize db:migrate

from the previous post? After model generation, we now have some default namings, like such fields as createdAt, so they are camelcased, but if you as me came from non-js world, you probably believe that your tables should be underscored! So let’s open the migration file (you can find it in db/migrations) and edit it to make it look like this:

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('books', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      title: {
        type: Sequelize.STRING
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('books');
  }
};

Here I changed our table name and createdAt & updatedAt fields. It is up to you which style to prefer, but I believe that camelcase should stay aside of a database.
Now you can go and change your model definition:

module.exports = (sequelize, DataTypes) => {
  var Book = sequelize.define('Book', {
    title: DataTypes.STRING
  }, { tableName: 'books', underscored: true });
  Book.associate = function(models) {
    // associations can be defined here
  };
  return Book;
};

Here you not only explicitly set the tableName, which I find a good approach, but also pass the parameter `underscored`, which will perceive your DB fields like default timestamps as underscored. First roll back your previous migration:

node_modules/.bin/sequelize db:migrate:undo

Ok, now you’re ready to re-migrate your migrations, with the same command as before:

node_modules/.bin/sequelize db:migrate

Lovely ๐Ÿ™‚

Adding a new model with relationships

Let’s move on to the new model. We’re building a book club, where you have all sorts of books in your system and visitors can list them, and registered users can also discuss them (per se “leave comments”). In this part of the tutorial we will build a relationship between books and comments, so every book may have many comments. To achieve it we need a relationship “has_many” and “belongs_to”. If you’re absolutely unfamiliar with the concept of “relationships” between models – I will include the link to more thorough official documentation. Reading it is not required to build your first app, but if you want to know all possible config options – that’s the only source of truth (better source is only the source code).
So, let’s create our second model:

node_modules/.bin/sequelize model:generate --name comment --attributes text:text,author:string,book_id:integer

If you came from Rails world, you might be confused by the syntax, all that commas look strange for us. One more thing to notice is that I won’t be able to find a proper way of generating migrations together with references data and underscored timestamp names. Because of that we should go to the newly created migration and change it to the following:

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('comments', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      text: {
        type: Sequelize.TEXT
      },
      author: {
        type: Sequelize.STRING
      },
      book_id: {
        type: Sequelize.INTEGER,
        references: {
          model: 'books',
          key: 'id'
        },
        onUpdate: 'cascade',
        onDelete: 'cascade'
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('comments');
  }
};

I changed createdAt and updatedAt to created_at and updated_at respectively. Also I added such params to the `book_id` field:

references: {
  model: 'books',
  key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'

Which as can be seen, says that `book_id` is not just a field, but a reference to the books model with the `id` key associated with this field. `Cascade` means that in case of book removal, all its comments will also be removed.
Now when the migrations part is done, feel free to migrate them, using the same command as in the previous post:

node_modules/.bin/sequelize db:migrate

Now go and change your model to make it look like this:

'use strict';
module.exports = (sequelize, DataTypes) => {
  var Comment = sequelize.define('comment', {
    text: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: true
      }
    },
    author: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: true
      }
    }
  }, { tableName: 'comments', underscored: true });
  comment.associate = function(models) {
    Comment.belongsTo(models.Book, { foreignKey: 'book_id', allowNull: false })
  };
  return Comment;
};

Here I changed `comment`->`Comment`, because it makes sense to have Comment model to start with capital letter, but the table with downcased, which you may see in the `tableName` option. One new thing we have here is `associate` function. In it we set one-to-many relationship to the Book, using the key `book_id`. Also, you may notice that we have validations on this model. Confusing thing is that it will still allow you to pass `null` value. And even more weird thing is that Sequelize has options like `notNull: true`, but it is deprecated. So in order to have a real validation on null value, go back to your migration (yep, undo it first), and add `allowNull: false` to your fileds definitions, f.e.:

text: {
  allowNull: false,
  type: Sequelize.STRING
}

How to test Sequelize models in console

As you may know, my main everyday language is Ruby, so I’m really have got used to `rails c` when I was using it on an everyday base. Now I’m working with Grape and I have pry-console set up for all my purposes and when I needed to test my Sequelize models, it confused me a lot. But it turned to be pretty easy.
First run `node` from your terminal, it will open node cli, the analog of Ruby’s `irb`.
Then in it you can first require your models:

const models = require('./src/models/index.js');

As you may notice, you should not require them individually, because your `models/index.js` file contains some glue code to make your models to be loaded. Then once your models loaded – you can go wild:

// will return you the result query and empty response, because you don't have any comments yet
models.Comment.findAll().then(console.log);
// will return you a validation error because the book_id, you're referencing to, doesn't exist
models.Comment.create({ text: 'foo', book_id: 100 }).then(comment => console.log(comment))
// will create a book and return you it's data
models.Book.create({ title: 'War and peace' }).then(book => console.log(book))
// let's assume that your brand new book had an id 1, so now you can use its id to create a comment to it
models.Comment.create({ text: 'oh my gosh, such a book!', author: 'reader', book_id: 1 }).then(comment => console.log(comment))

Please keep in mind, that I’m assuming that you have a code from the previous post.

That’s it, I hope it was useful to you, my friends. Expect the next post in a couple of weeks. Last time I’m writing slower because I’m deep in my another blogging project – CodeRunFun, which you can find on Youtube: https://www.youtube.com/channel/UCjYpgu18Nd6Wngbwp47MHaA. It is much less structured, but I try to post videos almost every day and tell you what interesting had I learn on a particular day. Please subscribe or just give your feedback ๐Ÿ™‚

See you soon!


One thought on “JS on Backend in 2018. Tutorial. Part 3. Relationships and Sequelize ORM tuning.

  1. Ha ha ha nice post, really cleared the part that migrations and models both have to be separated at all times, I always thought migrations were somehow automatically generated from models, the documentation makes no effort in mentioning this, I broke my head literally trying to figure out why relationships are not being created in migrations, why table name is still in caps and why model attributes dont have underscore, ha ha ha you said my main everyday language is Ruby, I am wondering what to answer for that, for many years it was Java for me, then for a few years JS and recently it has been both JS and Python, I feel at home with both languages lol

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.