Data Modeling with Sequelize

Chukwuemeka Chima
The JavaScript Dojo
7 min readJan 30, 2019

Welcome back, now we will discuss the REST API we will build so we can model the data objects needed. To get up to speed, please visit the previous articles in this series to follow along.

Migrations

We will be building a simple API to manage books and reviews on books. Our API will expose endpoints which will create, update, delete and retrieve books as well as endpoints to create reviews and modify reviews on the platform. We will not be covering authentication in this series.
Looking at this we can observe a relation between books and reviews. So a book will have many reviews and a review can belong to a single book, we will dive deep into that in the associations’ section of this article. For now, let us create the necessary migrations for this relation(application).

Copy and paste the code snippet below into your terminal, ensure you are in the project’s root directory.

$ node_modules/.bin/sequelize model:generate --name Book --attributes title:string,description:string,genre:enum,isbnNo:string,pages:integer$ node_modules/.bin/sequelize model:generate --name Review --attributes title:string,review:string,bookId:integer

This command just created four new files in the project. Two files in the migrations folder and two files: book.js and review.js in the models’ folder.
Let us examine the command we just used with its options. We used the model:generate command now which accepts two options to create a model and a matching migration to upgrade the state of our database.

  • — name — The name of the model to be created.
  • — attributes — The attributes or properties of the model.

Let’s take a closer look at the review migration file. This file exports an object with two properties up and down. Funny up and down isn’t it? 😀

  • up — Upgrades the database schema with the new changes. This property is a function that will be invoked with two arguments: queryInterface and sequelize. The queryInterface argument, just as the name is an interface/object for executing different queries on a database. The sequelize argument is an object which houses all the different data type definition supported by the Sequelize ORM.
  • down — Resets the database to its previous state before the up migration was run. So essentially it just reverts the database to how it was before that migration file was introduced.
    If we look closely at the file, we can see that a method on the queryInterface with the name createTable was invoked with two arguments. The arguments are the name of the table to be created as well as the attributes/properties of the table with their data type declarations and other constraints.

Let’s take a closer look at the second argument to queryInterface.createTable. This argument is an object with key-value pairs. The keys are the column names to be created on the table while the value is an object containing the constraints and data type of the column.
Below is what the first property of the attributes should look like.

id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
  • allowNull — is equivalent to the SQL NOT NULL constraint.
  • autoIncrement — is equivalent to the SQL AUTO_INCREMENT constraint in MySQL, SERIAL in PostgreSQL, and IDENTITY(1, 1) in MS SQL.
  • primaryKey — is equivalent to the SQL PRIMARY KEY constraint.
  • type — is used to denote the data type of the column.

Click here to see a list of data types supported by sequelize along with their declarations.

Now, we will update the bookId property in the attributes object with the code below.

bookId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Books',
key: 'id',
},
},
  • onDelete — setting this to CASCADE will cause all reviews related to a book to be deleted once the book it references gets deleted.
  • onUpdate — setting this to CASCADE will update the bookId column to the new value of the id on the book table if the id of the book it references is deleted.
  • references — is an object that contains information to point the foreign key to the table it references. The model property on the references object will have the name of the book table in our example above “Books”. The key property will have the name of the column(primary key) the foreign Key(reviewId) on the review table references.

Good, now our migration files have been modelled to update our database schema to suit our API needs. 🙌

Models

Now let’s examine our model files in the models’ directory. Let’s start with the book model.
The book model exports a function which accepts two parameters: sequelize and dataTypes.
The sequelize parameter has a method called define which when invoked with 2–3 parameters returns an instance of a sequelize model.

  • Book — this is the first argument accepted by the define method. It bears the name of the model which we can use to reference the model. The model will be stored in sequelize.models with this name(this is also the name we will use to import the model from sequelize’s index model file later).
  • attributes — this is an object which contains the list properties/attributes on a table to be exposed by the Book model. A lot of magic happens here like the mapping of properties to different columns on the table, adding constraints and custom validations to the models’ properties e.t.c.
  • options — this is the third argument supplied to the sequelize.define method. This allows for lots of configuration to be added to the model such as mapping the model to a different table or schema on the database. You can also add hooks, indexes and triggers here. However, in this article, we will only cover hooks in the options argument. You can check out other options, what they are used for and the values they accept here.

Hooks

Sequelize hooks provide an interface for programmers to modify certain behaviours or attributes on the model before a model action is triggered/executed.

{
hooks: {
afterFind: books => {
if (!books[0]) {
books.dataValues.createdAt = new Date(books.dataValues.createdAt).toUTCString();
books.dataValues.updatedAt = new Date(books.dataValues.updatedAt).toUTCString();
return books;
}
books = books.map(book => {
book.dataValues.createdAt = new Date(book.dataValues.createdAt).toUTCString();
book.dataValues.updatedAt = new Date(book.dataValues.updatedAt).toUTCString();
return book;
});
},
},
}

In the snippet above we defined a hook to modify the format of the createdAt and updatedAt date values to UTC format. You can do a lot more with hooks, like password hashing using the beforeCreate hook and lots more.

Associations

Associations are used in sequelize to denote the relation between models. Sequelize supports many types of relationships. However, in this article, we will focus on the One-To-Many association.
We will now define the associations between our Book and Review models. Replace the code in your Book associations section on book.js with the snippet below.

Book.associate = ({ Review }) => {
Book.hasMany(Review, {
foreignKey: 'bookId',
as: 'Reviews',
});
};

We will notice that a method(hasMany) on our book model was invoked with two arguments, Review and options.

  • Review — this is the model we are associating our Book model too.
  • options — this is an object which attributes to denote how this relation should be treated. The foreignKey attribute specifies the foreignKey on the Review table while the as attribute is an alias we will use to reference this association on later.

Replace the code in your Book associations section on book.js with the snippet below.

Review.associate = ({ Book }) => {
Review.belongsTo(Book, {
foreignKey: 'bookId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
as: 'Book',
});
};

We will notice that a method(belongsTo) on our review model was invoked with two arguments, Book and options.

  • Book — this is the model we are associating our Review model too.
  • options — same as above in Book association. However, two new attributes onDelete and onUpdate are now present on the options for belongs to. onDelete is set to CASCADE. This will cause all reviews related to a book to be deleted once the book is deleted. onUpdate set to CASCADE will update the foreign key(bookId) on the reviews if the primary key(id) on the book changes.

Scopes

Scopes provide a way to define commonly used queries. It can include finders like where, include, limit e.t.c. Scopes are defined on the model options(the third argument supplied to the sequelize.define method.
Below, we will define scopes using sequelize operators to enable us to query fictional and non-fictional books without writing them always. We will cover operators in the next article on CRUD operations with sequelize.
Copy and paste the snippet below into the options on the book.js file after the hooks declaration.

scopes: {
nonFiction: {
where: {
genre: {
Op.in]: ['Biography', 'Memoir', 'Essay', 'Textbook'],
},
},
},
fiction: {
where: {
genre: {
[Op.in]: ['Fable', 'Fiction', 'Mythology', 'Adventure'],
},
},
},
},

Validations

Now we will be adding a couple of validations to our model attributes. Sequelize uses the validator.js library for validations, so you can use all of the validator’s validations. You can also add your own custom validations not supported by validator.js. Below, we will define validations for the Book model.

  • title — is a not null string which contains 2 to 100 characters.
  • description — is a not null string which contains 10 to 250 characters.
  • isbn — is a not null string which contains 10 to 13 characters.
  • pages — is an optional number which must be an integer.

Wow, congratulations on making it this far. 👏 That was definitely so much information. This is what your model and migration files should now look like. I will love to hear your comments. Feel free to respond.

Migration files

Model files

Click the link below to view the complete code for this article and other articles in this series. Don’t forget to give us a star if this was helpful to you.

--

--

Chukwuemeka Chima
The JavaScript Dojo

Frontend Engineer — In love with the creation of digital solutions to make the world a better place.